FreeRTOS issues - threads start but won't wake on notifications - nRF52832 and S132 SoftDevice (SDK Version 17.1.0)

Hi,

I'm trying to implement FreeRTOS on nRF52832 using the S132 SoftDevice and SDK version 17.1.0.

This is meant to read multiple sensors at a configured frequency - each thread is triggered from an ISR via a notification.

However, although all of my threads seem to start and yield OK, they don't seem to be waking on notifications or message queue events.

The scheduler seems to be running, and software timers work OK. I believe that I have configured the priorities correctly for Cortex-M4.

Can anyone please advise as to how I should go about diagnosing this? Please find code snippets below - is there anything obviously wrong?

Many thanks,

Adam

/**************************************************************
 * @brief  Main function, application entry point.
 * @notes  None.
 * @param  None.
 **************************************************************/
int main(void) {
	bool erase_bonds;
	
	log_init();
	
	NRF_LOG_INFO("\r\n************* FIRMWARE v%d.%d.%d *************", FIRM_VER_MAJOR, FIRM_VER_MINOR, FIRM_VER_PATCH);
	NRF_LOG_INFO("DEVICE ID: %08X%08X", NRF_FICR->DEVICEID[0], NRF_FICR->DEVICEID[1]);
	
	clock_init();
	
#if (USE_OS)
	os_init();
#endif
	
	set_system_state(INITIALISING);
	
	#if USE_UART_OUTPUT
	uart_init();
	#endif
	
	PRINT("\r\n\r\n************* FIRMWARE v%d.%d.%d *************\r\n", FIRM_VER_MAJOR, FIRM_VER_MINOR, FIRM_VER_PATCH);
	PRINT("DEVICE ID: %08X%08X\r\n", NRF_FICR->DEVICEID[0], NRF_FICR->DEVICEID[1]);

	board_init(&erase_bonds);
		
#if (USE_ADC)
	saadc_init();
    saadc_sampling_event_init();
#endif

#if (USE_I2C)
    if (init_I2C() == NO_ERROR) {
		NRF_LOG_INFO("I2C peripheral initialised successfully.");
	}
	else {
		NRF_LOG_INFO("I2C peripheral initialisation failed!");
	}
#endif
	
#if (TEST_SENSORS == true)
	initialise_sensors();
	test_sensors();
#endif

#if (USE_ADC == true)
	saadc_sampling_event_enable();
    delay_ms(ADC_READ_DURATION_MS);
#endif

	ble_init();

#if (!USE_FREERTOS)
	advertising_start((void *)&erase_bonds);
#endif

#if (USE_OS)
	os_start(&erase_bonds);
#else	
	// Enter main loop.
	for (;;) {
        idle_state_handle();
	}
#endif
}

/* ********************* GLOBAL DEFINES ********************* */

#ifndef DEBUG
#define DEBUG
#endif

#define FIRM_VER_MAJOR        		1
#define FIRM_VER_MINOR        		0
#define FIRM_VER_PATCH        		0

#define USE_WDT               		false	// Enable a watchdog timer.
#define USE_ADC						false	// Use the SAADC to read analogue channels.
#define USE_SENSOR_POWER_CTRL		true	// Use sensor power control during runtime (powered on by default).
#define USE_DEEP_SLEEP				false	// Use deep sleep mode

#ifndef USE_RTX5
#ifdef RTX5
#define USE_RTX5					true
#else
#define USE_RTX5					false
#endif
#endif
#ifndef USE_FREERTOS
#ifdef FREERTOS
#define USE_FREERTOS				true
#else
#define USE_FREERTOS				false
#endif
#endif
#define USE_OS						(RTX5 || FREERTOS)
#define START_SENSOR_A_THREAD		false
#define START_SENSOR_B_THREAD		false
#define START_SENSOR_C_THREAD		false
#define START_SENSOR_D_THREAD		false

#define USE_BMA400            		false
#define USE_BME680            		false
#define USE_SHTC3             		true
#define USE_MS5607             		true
#define USE_TSL2X7X					true
#define USE_MXC400X					true
#define USE_I2C						(USE_BMA400 || USE_BME680 || USE_SHTC3 || USE_MS5607 || USE_TSL2X7X || USE_MXC400X)
#ifndef DEBUG
#define USE_UART_OUTPUT				false
#else
#define USE_UART_OUTPUT				false
#endif

#define TEST_STATUS_DATA			false
#define TEST_BMA400            		false
#define TEST_BME680            		false
#define TEST_SHTC3             		false
#define TEST_MS5607             	false
#define TEST_TSL2X7X				false
#define TEST_MXC400X				false
#define TEST_SENSORS				(TEST_BMA400 || TEST_BME680 || TEST_SHTC3 || TEST_MS5607 || TEST_TSL2X7X || TEST_MXC400X)

#define INIT_DELAY_MS				500
#define TEST_DELAY_MS				2000
#define SENSOR_POWER_DELAY_MS		100

#if (((TEST_BMA400 == true) && (USE_BMA400 == false)) || ((TEST_BME680 == true) && (USE_BME680 == false)) || ((TEST_SHTC3 == true) && (USE_SHTC3 == false)) || ((TEST_MS5607 == true) && (USE_MS5607 == false)) || ((TEST_TSL2X7X == true) && (USE_TSL2X7X == false)) || ((TEST_MXC400X == true) && (USE_MXC400X == false)))
#error "Sensor testing requires the corresponding sensor to be set to active!"
#endif

#define BATTERY_MEAS_INT_MS         10000
#define POLL_PERIOD_DEFAULT_MS		10

#define ADC_FSR_ARRAY				false

#define ADC_READ_DURATION_MS   		50	
#define ADC_SEND_DURATION_MS		1000
#define ADC_NUM_BANKS				1		/* Number of sensor banks */
#define ADC_NUM_CHANNELS        	1       /* Number of ADC channels used per sensor bank */
#define ADC_NUM_SENSORS				(ADC_NUM_BANKS * ADC_NUM_CHANNELS)

#define PIN_SENSOR_VDD_ENABLE   	NRF_GPIO_PIN_MAP(0, 9)
#define PIN_STATUS_LED_A   			NRF_GPIO_PIN_MAP(0, 10)
#define PIN_INTERRUPT_TSL2X7X		NRF_GPIO_PIN_MAP(0, 29)
#define PIN_INTERRUPT_MXC400X		NRF_GPIO_PIN_MAP(0, 28)

#define NUM_SENSORS					4
#define NUM_SENSOR_DATA_CHANNELS	4

#define LED_BEHAVIOUR_OFF			BSP_INDICATE_IDLE
#define LED_BEHAVIOUR_CONNECTED		BSP_INDICATE_CONNECTED
#define LED_BEHAVIOUR_ADV_FAST		BSP_INDICATE_ADVERTISING
#define LED_BEHAVIOUR_ADV_SLOW		BSP_INDICATE_ADVERTISING_SLOW
#define LED_BEHAVIOUR_NO_ALERT		BSP_INDICATE_IDLE
#define LED_BEHAVIOUR_MILD_ALERT	BSP_INDICATE_IDLE
#define LED_BEHAVIOUR_HIGH_ALERT	BSP_INDICATE_IDLE

#define DEAD_BEEF                   0xDEADBEEF			/**< Value used as error code on stack dump, can be used to identify stack location on stack unwind. */

#define UART_TX_BUF_SIZE            512					/**< UART TX buffer size. */
#define UART_RX_BUF_SIZE            256					/**< UART RX buffer size. */

#if USE_UART_OUTPUT
#  define PRINT 					printf
#else
#  define PRINT 					fake_printf
#endif

/* ******************** ENUMERATED TYPES ******************** */

typedef enum {
    UNKNOWN = 0,
    POWERED_OFF = 1,
	POWERED_ON = 2,
} etSensorStatus;

typedef enum {
    INITIALISING = 0,
    STANDBY = 1,
	MEASURING = 2,
	COMPLETE = 3,
	SHUTTING_DOWN = 4,
} etSystemState;

/* ************************ STRUCTURES ************************ */

typedef struct {
	bool enabled;				// Sensor enable.
	etSensorStatus status;		// Sensor status.
	bool mode;					// If "true", sensor is polled; else, sensor is interrupt-driven (if supported).
	uint32_t read_period;		// Sensor read period (only used if "mode" is "true").
} sensorInformation_t;

/**************************************************************
 * @brief  Function for initializing the low-frequency and high-frequency clocks.
 * @param  None.
 **************************************************************/
static void clock_init(void) {
	NRF_LOG_INFO("Low frequency clock init!");
	
    if (!nrf_drv_clock_lfclk_is_running()) {
        APP_ERROR_CHECK(nrf_drv_clock_init());
        nrf_drv_clock_lfclk_request(NULL);
        while (!nrf_drv_clock_lfclk_is_running());
    }
	
	NRF_CLOCK->TASKS_HFCLKSTART = 1;
    while (NRF_CLOCK->EVENTS_HFCLKSTARTED == 0);
}


/**************************************************************
 * @brief  Function for initializing GPIO.
 * @param  None.
 **************************************************************/
void gpio_init(void) {
    ret_code_t err_code;
	
	NRF_LOG_INFO("GPIO init!");

	// Initialise GPIOTE.
    err_code = nrf_drv_gpiote_init();
    APP_ERROR_CHECK(err_code);

	// Initialise the sensor power control line.
    nrf_drv_gpiote_out_config_t sensor_control_line_config = GPIOTE_CONFIG_OUT_SIMPLE(false);
    err_code = nrf_drv_gpiote_out_init(PIN_SENSOR_VDD_ENABLE, &sensor_control_line_config);
    APP_ERROR_CHECK(err_code);
	
	// Initialise the LED control line.
	nrf_drv_gpiote_out_config_t led_control_line_config = GPIOTE_CONFIG_OUT_SIMPLE(false);
    err_code = nrf_drv_gpiote_out_init(PIN_STATUS_LED_A, &led_control_line_config);
    APP_ERROR_CHECK(err_code);

	// Initialise TSL2X7X interrupt line.
    nrf_drv_gpiote_in_config_t tsl2x7x_interrupt_line_config = GPIOTE_CONFIG_IN_SENSE_TOGGLE(true);
    tsl2x7x_interrupt_line_config.pull = NRF_GPIO_PIN_PULLUP;
    err_code = nrf_drv_gpiote_in_init(PIN_INTERRUPT_TSL2X7X, &tsl2x7x_interrupt_line_config, tsl2x7x_interrupt_handler);
    APP_ERROR_CHECK(err_code);
	
	// Initialise MXC400X interrupt line.
    nrf_drv_gpiote_in_config_t mxc400x_interrupt_line_config = GPIOTE_CONFIG_IN_SENSE_TOGGLE(true);
    mxc400x_interrupt_line_config.pull = NRF_GPIO_PIN_PULLUP;
    err_code = nrf_drv_gpiote_in_init(PIN_INTERRUPT_MXC400X, &mxc400x_interrupt_line_config, mxc400x_interrupt_handler);
    APP_ERROR_CHECK(err_code);
}


/**************************************************************
 * @brief  Function for handling hard faults.
 * @param  None.
 **************************************************************/
void HardFault_Handler(void) {
    NVIC_SystemReset();
}


/**************************************************************
 * @brief  Assert callback function.
 * @param[in] id    Fault identifier. See @ref NRF_FAULT_IDS.
 * @param[in] pc    The program counter of the instruction that triggered the fault, or 0 if
 *                  unavailable.
 * @param[in] info  Optional additional information regarding the fault. Refer to each fault
 *                  identifier for details.
 **************************************************************/
void app_error_fault_handler(uint32_t id, uint32_t pc, uint32_t info) {
    NVIC_SystemReset();
}


/**************************************************************
 * @brief  Function for assert macro callback.
 * @notes  This function will be called in case of an assert in the SoftDevice.
 * @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] p_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   WDT events handler.
 * @details After two cycles of 32768[Hz] clock in WDT interrupt, reset occurs.
 * @param   None.
 **************************************************************/
#if USE_WDT
void wdt_event_handler(void) {
    // Do nothing.
}
#endif


/**************************************************************
 * @brief  Function configures the watchdog timer.
 * @param  None.
 **************************************************************/
#if USE_WDT
void wdt_init(void) {
    nrf_drv_wdt_config_t config = NRF_DRV_WDT_DEAFULT_CONFIG;
    ret_code_t err_code;
	
	NRF_LOG_INFO("WDT init!");
		
	err_code = nrf_drv_wdt_init(&config, wdt_event_handler);
    APP_ERROR_CHECK(err_code);

    err_code = nrf_drv_wdt_channel_alloc(&wdt_channel_id);
    APP_ERROR_CHECK(err_code);
}
#endif


/**************************************************************
 * @brief  Function enables the watchdog timer.
 * @param  None.
 **************************************************************/
#if USE_WDT
void wdt_enable(void) {
    nrf_drv_wdt_enable();
}
#endif


/**************************************************************
 * @brief  Function feeds the watchdog timer.
 * @param  None.
 **************************************************************/
#if USE_WDT
void wdt_feed(void) {
    nrf_drv_wdt_channel_feed(wdt_channel_id);
}
#endif


/**************************************************************
 * @brief       Function for measuring the battery level.
 * @details     None.
 * @notes       None.
 * @param[in]   None.
 * @param[out]  None.
 **************************************************************/
void measure_battery_level(void) {
	ret_code_t err_code;
	
	NRF_LOG_INFO("Measuring battery level!");
	
    saadc_init();
    err_code = nrf_drv_saadc_sample();
    APP_ERROR_CHECK(err_code);
}


/**************************************************************
 * @brief  Function for initializing the board.
 * @notes  None.
 * @param  None.
 **************************************************************/
void board_init(bool *erase_bonds) {
	NRF_LOG_INFO("Board init!");
	
#if (USE_DEEP_SLEEP)
    deep_sleep_init();
#endif
	
	gpio_init();
	
    os_timers_init();

    buttons_leds_init(erase_bonds);

    power_management_init();

	//flash_init();
	
#if (USE_WDT == true)
    wdt_init();
    wdt_enable();
#endif
}


/**************************************************************
 * @brief  Function for initializing deep sleep mode.
 * @notes  None.
 * @param  None.
 **************************************************************/
void deep_sleep_init(void) {
	SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;
}


/**************************************************************
 * @brief  Function for uninitializing the board.
 * @notes  None.
 * @param  None.
 **************************************************************/
void board_deinit(void) {
	NRF_LOG_INFO("Board deinit!");
	
	nrf_drv_gpiote_uninit();
	sensor_power_control(false);
	led_A_power_control(false);
}


/**************************************************************
 * @brief  Function for putting the chip into sleep mode.
 * @notes  This function will not return.
 * @param  None.
 **************************************************************/
void sleep_mode_enter(void) {
    ret_code_t err_code;
	
	NRF_LOG_INFO("Entering sleep mode!");

	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);
	
	while(1);	// Catch unintended soft-resets when in debug mode (i.e. due to using debugger or RTT Viewer).
}


/**************************************************************
 * @brief  Function for initializing the UART module.
 * @notes  None.
 * @param  None.
 **************************************************************/
void uart_init(void) {
    ret_code_t err_code;
	
	NRF_LOG_INFO("UART init!");
	
    app_uart_comm_params_t const comm_params = {
        .rx_pin_no    = RX_PIN_NUMBER,
        .tx_pin_no    = TX_PIN_NUMBER,
        .rts_pin_no   = RTS_PIN_NUMBER,
        .cts_pin_no   = CTS_PIN_NUMBER,
        .flow_control = APP_UART_FLOW_CONTROL_DISABLED,
        .use_parity   = false,
#if defined (UART_PRESENT)
        .baud_rate    = NRF_UART_BAUDRATE_115200
#else
        .baud_rate    = NRF_UARTE_BAUDRATE_115200
#endif
    };

    APP_UART_FIFO_INIT(&comm_params, UART_RX_BUF_SIZE, UART_TX_BUF_SIZE, uart_event_handle, APP_IRQ_PRIORITY_LOWEST, err_code);
    APP_ERROR_CHECK(err_code);
}


/**************************************************************
 * @brief  Function for handling app_uart events.
 * @notes  This function will receive a single character from the app_uart module and append it to
 *         a string. The string will be be sent over BLE when the last character received was a
 *         'new line' '\n' (hex 0x0A) or if the string has reached the maximum data length.
 * @param  None.
 **************************************************************/
void uart_event_handle(app_uart_evt_t * p_event) {
    uint8_t data_array[BLE_NUS_MAX_DATA_LEN];
    uint8_t index = 0;

    switch (p_event->evt_type) {
        case APP_UART_DATA_READY:
            UNUSED_VARIABLE(app_uart_get(&data_array[index]));
            index++;
			ble_send_nus_data(data_array, &index);
            break;
        case APP_UART_COMMUNICATION_ERROR:
            APP_ERROR_HANDLER(p_event->data.error_communication);
            break;
        case APP_UART_FIFO_ERROR:
            APP_ERROR_HANDLER(p_event->data.error_code);
            break;
        default:
            break;
    }
}


/**************************************************************
 * @brief  Function for initializing the nrf log module.
 * @notes  None.
 * @param  None.
 **************************************************************/
void log_init(void) {
    ret_code_t err_code;

	err_code = NRF_LOG_INIT(NULL);
    APP_ERROR_CHECK(err_code);

    NRF_LOG_DEFAULT_BACKENDS_INIT();
}


/**************************************************************
 * @brief  Function for initializing power management.
 * @notes  None.
 * @param  None.
 **************************************************************/
void power_management_init(void) {
    ret_code_t err_code;
	
	NRF_LOG_INFO("Power management init!");
	
    err_code = nrf_pwr_mgmt_init();
    APP_ERROR_CHECK(err_code);
}


/**************************************************************
 * @brief  Function for handling the idle state (main loop).
 * @notes  If there is no pending log operation, then sleep until next the next event occurs.
 * @param  None.
 **************************************************************/
void idle_state_handle(void) {
#if (USE_WDT == true)
	wdt_feed();
#endif
    if (NRF_LOG_PROCESS() == false) {
        nrf_pwr_mgmt_run();
    }
}

/**************************************************************
 * @brief  Function initialises Keil RTX5 operating system.
 * @notes  None.
 * @param  void
 * @return void
 **************************************************************/
void os_kernel_init(void) {
#if RTX5
	osKernelInitialize();
	NVIC_SetPriorityGrouping(3);
#endif
}


/**************************************************************
 * @brief  Function initialises OS mutexes.
 * @notes  None.
 * @param  void
 * @return void
 **************************************************************/
void os_mutex_init(void) {
#if USE_RTX5
	i2c_Mutex = osMutexNew(NULL);
	if (i2c_Mutex == NULL) {
		app_error_fault_handler(NULL, NULL, NULL);
	}
#elif USE_FREERTOS
	#if FRERTOS_USE_STATIC_ALLOCATION
	m_i2c_mutex = xSemaphoreCreateMutexStatic(&xMutexBuffer);
	if (m_i2c_mutex == NULL) {
		app_error_fault_handler(NULL, NULL, NULL);
	}
	#else
	m_i2c_mutex = xSemaphoreCreateMutex();
	if (m_i2c_mutex == NULL) {
		app_error_fault_handler(NULL, NULL, NULL);
	}
	#endif
#endif
}


/**************************************************************
 * @brief  Function initialises OS message queues.
 * @notes  None.
 * @param  void
 * @return void
 **************************************************************/
void os_msg_queue_init(void) {
#if USE_RTX5
	mediator_MsgQueue_id = osMessageQueueNew(MSGQUEUE_OBJECTS, sizeof(MSGQUEUE_OBJ_t), NULL);
	sensorA_MsgQueue_id = osMessageQueueNew(MSGQUEUE_OBJECTS, sizeof(MSGQUEUE_OBJ_t), NULL);
	sensorB_MsgQueue_id = osMessageQueueNew(MSGQUEUE_OBJECTS, sizeof(MSGQUEUE_OBJ_t), NULL);
	sensorC_MsgQueue_id = osMessageQueueNew(MSGQUEUE_OBJECTS, sizeof(MSGQUEUE_OBJ_t), NULL);
	sensorD_MsgQueue_id = osMessageQueueNew(MSGQUEUE_OBJECTS, sizeof(MSGQUEUE_OBJ_t), NULL);
	if ((mediator_MsgQueue_id == NULL) || (sensorA_MsgQueue_id == NULL) || (sensorB_MsgQueue_id == NULL) || (sensorC_MsgQueue_id == NULL) || (sensorD_MsgQueue_id == NULL)) {
		app_error_fault_handler(NULL, NULL, NULL);
	}
#elif USE_FREERTOS
	#if FRERTOS_USE_STATIC_ALLOCATION
	m_mediator_queue = xQueueCreateStatic(MSGQUEUE_OBJECTS, sizeof(struct MSGQUEUE_OBJ_t *), xMediatorQueueStorage, &xMediatorQueueStructure);
	m_sensorA_queue = xQueueCreateStatic(MSGQUEUE_OBJECTS, sizeof(struct MSGQUEUE_OBJ_t *), xSensorAQueueStorage, &xSensorAQueueStructure);
	m_sensorB_queue = xQueueCreateStatic(MSGQUEUE_OBJECTS, sizeof(struct MSGQUEUE_OBJ_t *), xSensorBQueueStorage, &xSensorBQueueStructure);
	m_sensorC_queue = xQueueCreateStatic(MSGQUEUE_OBJECTS, sizeof(struct MSGQUEUE_OBJ_t *), xSensorCQueueStorage, &xSensorCQueueStructure);
	m_sensorD_queue = xQueueCreateStatic(MSGQUEUE_OBJECTS, sizeof(struct MSGQUEUE_OBJ_t *), xSensorDQueueStorage, &xSensorDQueueStructure);
	if ((m_mediator_queue == NULL) || (m_sensorA_queue == NULL) || (m_sensorB_queue == NULL) || (m_sensorC_queue == NULL) || (m_sensorD_queue == NULL)) {
		app_error_fault_handler(NULL, NULL, NULL);
	}
	#else
	m_mediator_queue = xQueueCreate(MSGQUEUE_OBJECTS, sizeof(struct MSGQUEUE_OBJ_t *));
	m_sensorA_queue = xQueueCreate(MSGQUEUE_OBJECTS, sizeof(struct MSGQUEUE_OBJ_t *));
	m_sensorB_queue = xQueueCreate(MSGQUEUE_OBJECTS, sizeof(struct MSGQUEUE_OBJ_t *));
	m_sensorC_queue = xQueueCreate(MSGQUEUE_OBJECTS, sizeof(struct MSGQUEUE_OBJ_t *));
	m_sensorD_queue = xQueueCreate(MSGQUEUE_OBJECTS, sizeof(struct MSGQUEUE_OBJ_t *));
	if ((m_mediator_queue == NULL) || (m_sensorA_queue == NULL) || (m_sensorB_queue == NULL) || (m_sensorC_queue == NULL) || (m_sensorD_queue == NULL)) {
		app_error_fault_handler(NULL, NULL, NULL);
	}
	#endif
#endif
}


/**************************************************************
 * @brief  Function initialises OS threads.
 * @notes  None.
 * @param  void
 * @return void
 **************************************************************/
void os_thread_init(void) {
	// Initialise logger thread.
#if USE_RTX5
	const osThreadAttr_t logger_thread_attr = {
		.name = "LOGGER",
		.priority = osPriorityHigh,
	};
	logger_thread_id = osThreadNew(app_thread_logger, NULL, &logger_thread_attr);
	if (logger_thread_id == NULL) {
		NRF_LOG_INFO("Logger thread initialisation failed!");
		app_error_fault_handler(NULL, NULL, NULL);
	}
	else {
		NRF_LOG_INFO("Logger thread initialisation successful.");
	}
#elif USE_FREERTOS
	#if FRERTOS_USE_STATIC_ALLOCATION
	m_logger_thread = xTaskCreateStatic(app_thread_logger, "LOGR", FRERTOS_THREAD_STACK_DEPTH, NULL, LOGGER_THREAD_PRIORITY, xLoggerTaskStack, &xLoggerTaskBuffer);
	if (m_logger_thread == NULL) {
		NRF_LOG_INFO("Logger thread initialisation failed!");
        APP_ERROR_HANDLER(NRF_ERROR_NO_MEM);
    }
	NRF_LOG_INFO("Logger thread initialisation successful.");
	#else
	if (xTaskCreate(app_thread_logger, "LOGR", FRERTOS_THREAD_STACK_DEPTH, NULL, FRERTOS_THREAD_DEFAULT_PRIORITY, &m_logger_thread) != pdPASS) {
		NRF_LOG_INFO("Logger thread initialisation failed!");
        APP_ERROR_HANDLER(NRF_ERROR_NO_MEM);
    }
	NRF_LOG_INFO("Logger thread initialisation successful.");
	#endif
#endif
	
	// Initialise mediator thread.
#if USE_RTX5
	const osThreadAttr_t mediator_thread_attr = {
		.name = "MEDIATOR",
		.priority = osPriorityHigh,
	};
	mediator_thread_id = osThreadNew(app_thread_mediator, NULL, &mediator_thread_attr);
	if (mediator_thread_id == NULL) {
		NRF_LOG_INFO("Mediator thread initialisation failed!");
		app_error_fault_handler(NULL, NULL, NULL);
	}
	else {
		NRF_LOG_INFO("Mediator thread initialisation successful.");
	}
#elif USE_FREERTOS
	#if FRERTOS_USE_STATIC_ALLOCATION
	m_mediator_thread = xTaskCreateStatic(app_thread_mediator, "MEDR", FRERTOS_THREAD_STACK_DEPTH, NULL, MEDIATOR_THREAD_PRIORITY, xMediatorTaskStack, &xMediatorTaskBuffer);
	if (m_mediator_thread == NULL) {
		NRF_LOG_INFO("Mediator thread initialisation failed!");
        APP_ERROR_HANDLER(NRF_ERROR_NO_MEM);
    }
	NRF_LOG_INFO("Mediator thread initialisation successful.");
	#else
	if (xTaskCreate(app_thread_mediator, "MEDR", FRERTOS_THREAD_STACK_DEPTH, NULL, FRERTOS_THREAD_DEFAULT_PRIORITY, &m_mediator_thread) != pdPASS) {
		NRF_LOG_INFO("Mediator thread initialisation failed!");
        APP_ERROR_HANDLER(NRF_ERROR_NO_MEM);
    }
	NRF_LOG_INFO("Mediator thread initialisation successful.");
	#endif
#endif
	
	// Initialise sensor threads.
#if USE_RTX5
	const osThreadAttr_t sensorA_thread_attr = {
		.name = "SENSOR A",
		.priority = osPriorityAboveNormal,
	};
	sensorA_thread_id = osThreadNew(app_thread_sensorA, NULL, &sensorA_thread_attr);
	const osThreadAttr_t sensorB_thread_attr = {
		.name = "SENSOR B",
		.priority = osPriorityAboveNormal,
	};
	sensorB_thread_id = osThreadNew(app_thread_sensorB, NULL, &sensorB_thread_attr);
	const osThreadAttr_t sensorC_thread_attr = {
		.name = "SENSOR C",
		.priority = osPriorityAboveNormal,
	};
	sensorC_thread_id = osThreadNew(app_thread_sensorC, NULL, &sensorC_thread_attr);
	const osThreadAttr_t sensorD_thread_attr = {
		.name = "SENSOR D",
		.priority = osPriorityAboveNormal,
	};
	sensorD_thread_id = osThreadNew(app_thread_sensorD, NULL, &sensorD_thread_attr);
	if ((sensorA_thread_id == NULL) || (sensorB_thread_id == NULL) || (sensorC_thread_id == NULL) || (sensorD_thread_id == NULL)) {
		NRF_LOG_INFO("Sensor thread initialisation failed!");
		app_error_fault_handler(NULL, NULL, NULL);
	}
	else {
		NRF_LOG_INFO("Sensor thread initialisation successful.");
	}
#elif USE_FREERTOS
	#if FRERTOS_USE_STATIC_ALLOCATION
	#if START_SENSOR_A_THREAD
	m_sensorA_thread = xTaskCreateStatic(app_thread_sensorA, "SR_A", FRERTOS_THREAD_STACK_DEPTH, NULL, SENSORA_THREAD_PRIORITY, xSensorATaskStack, &xSensorATaskBuffer);
	if (m_sensorA_thread == NULL) {
		NRF_LOG_INFO("Sensor A thread initialisation failed!");
        APP_ERROR_HANDLER(NRF_ERROR_NO_MEM);
    }
	NRF_LOG_INFO("Sensor A thread initialisation successful.");
	#endif
	
	#if START_SENSOR_B_THREAD
	m_sensorB_thread = xTaskCreateStatic(app_thread_sensorB, "SR_B", FRERTOS_THREAD_STACK_DEPTH, NULL, SENSORB_THREAD_PRIORITY, xSensorBTaskStack, &xSensorBTaskBuffer);
	if (m_sensorB_thread == NULL) {
		NRF_LOG_INFO("Sensor B thread initialisation failed!");
        APP_ERROR_HANDLER(NRF_ERROR_NO_MEM);
    }
	NRF_LOG_INFO("Sensor B thread initialisation successful.");
	#endif
	
	#if START_SENSOR_C_THREAD
	m_sensorC_thread = xTaskCreateStatic(app_thread_sensorC, "SR_C", FRERTOS_THREAD_STACK_DEPTH, NULL, SENSORC_THREAD_PRIORITY, xSensorCTaskStack, &xSensorCTaskBuffer);
	if (m_sensorC_thread == NULL) {
		NRF_LOG_INFO("Sensor C thread initialisation failed!");
        APP_ERROR_HANDLER(NRF_ERROR_NO_MEM);
    }
	NRF_LOG_INFO("Sensor C thread initialisation successful.");
	#endif
	
	#if START_SENSOR_D_THREAD
	m_sensorD_thread = xTaskCreateStatic(app_thread_sensorD, "SR_D", FRERTOS_THREAD_STACK_DEPTH, NULL, SENSORD_THREAD_PRIORITY, xSensorDTaskStack, &xSensorDTaskBuffer);
	if (m_sensorD_thread == NULL) {
		NRF_LOG_INFO("Sensor D thread initialisation failed!");
        APP_ERROR_HANDLER(NRF_ERROR_NO_MEM);
    }
	NRF_LOG_INFO("Sensor D thread initialisation successful.");
	#endif
	#else
	#if START_SENSOR_A_THREAD
	if (xTaskCreate(app_thread_sensorA, "SR_A", FRERTOS_THREAD_STACK_DEPTH, NULL, FRERTOS_THREAD_DEFAULT_PRIORITY, &m_sensorA_thread) != pdPASS) {
		NRF_LOG_INFO("Sensor A thread initialisation failed!");
        APP_ERROR_HANDLER(NRF_ERROR_NO_MEM);
    }
	NRF_LOG_INFO("Sensor A thread initialisation successful.");
	#endif
	
	#if START_SENSOR_B_THREAD
	if (xTaskCreate(app_thread_sensorB, "SR_B", FRERTOS_THREAD_STACK_DEPTH, NULL, FRERTOS_THREAD_DEFAULT_PRIORITY, &m_sensorB_thread) != pdPASS) {
		NRF_LOG_INFO("Sensor B thread initialisation failed!");
        APP_ERROR_HANDLER(NRF_ERROR_NO_MEM);
    }
	NRF_LOG_INFO("Sensor B thread initialisation successful.");
	#endif
	
	#if START_SENSOR_C_THREAD
	if (xTaskCreate(app_thread_sensorC, "SR_C", FRERTOS_THREAD_STACK_DEPTH, NULL, FRERTOS_THREAD_DEFAULT_PRIORITY, &m_sensorC_thread) != pdPASS) {
		NRF_LOG_INFO("Sensor C thread initialisation failed!");
        APP_ERROR_HANDLER(NRF_ERROR_NO_MEM);
    }
	NRF_LOG_INFO("Sensor C thread initialisation successful.");
	#endif
	
	#if START_SENSOR_D_THREAD
	if (xTaskCreate(app_thread_sensorD, "SR_D", FRERTOS_THREAD_STACK_DEPTH, NULL, FRERTOS_THREAD_DEFAULT_PRIORITY, &m_sensorD_thread) != pdPASS) {
		NRF_LOG_INFO("Sensor D thread initialisation failed!");
        APP_ERROR_HANDLER(NRF_ERROR_NO_MEM);
    }
	NRF_LOG_INFO("Sensor D thread initialisation successful.");
	#endif
	#endif
#endif
}


/**************************************************************
 * @brief  Function starts the RTX5 kernel.
 * @notes  None.
 * @param  void
 * @return void
 **************************************************************/
void os_start(bool *erase_bonds) {
#if USE_RTX5
	osKernelStart();
#elif USE_FREERTOS
	// Create a FreeRTOS task for the BLE stack - this will run advertising_start() before entering its loop.
    nrf_sdh_freertos_init(advertising_start, erase_bonds);
    vTaskStartScheduler();
#endif
	for (;;) {
        APP_ERROR_HANDLER(NRF_ERROR_FORBIDDEN);		// Kernel start should not return unless RAM could not be allocated.
    }
}


/**************************************************************
 * @brief  Function initialises RTX5 dependencies on system startup.
 * @notes  None.
 * @param  void
 * @return void
 **************************************************************/
void os_on_startup(void) {
#if USE_RTX5
	SystemCoreClockUpdate();
#endif
}


/**************************************************************
 * @brief  Function initialises the OS.
 * @notes  None.
 * @param  void
 * @return void
 **************************************************************/
void os_init(void) {
	os_on_startup();
	os_kernel_init();
	os_mutex_init();
	os_msg_queue_init();
	os_thread_init();
}


/**************************************************************
 * @brief  Function yields a thread.
 * @notes  None.
 * @param  void
 * @return void
 **************************************************************/
void os_thread_yield(void) {
	#if USE_RTX5
	osThreadYield();
	#elif USE_FREERTOS
	vTaskSuspend(NULL);
	taskYIELD();
	#endif
}


/**************************************************************
 * @brief  Function acquires the I2C mutex.
 * @notes  None.
 * @param  void
 * @return void
 **************************************************************/
bool os_acquire_i2c_mutex(void) {
#if USE_RTX5
	osStatus_t status_OS;
#elif USE_FREERTOS
	BaseType_t status_OS;
#endif
	
#if USE_RTX5
	status_OS = osMutexAcquire(i2c_Mutex, osWaitForever);
	if (status_OS == osOK) {
		return true;
	}
	else {
		return false;
	}
#elif USE_FREERTOS
	status_OS = xSemaphoreTake(m_i2c_mutex, portMAX_DELAY);
	if (status_OS == pdTRUE) {
		return true;
	}
	else {
		return false;
	}
#endif
}


/**************************************************************
 * @brief  Function releases the I2C mutex.
 * @notes  None.
 * @param  void
 * @return void
 **************************************************************/
bool os_release_i2c_mutex(void) {
	#if USE_RTX5
	osMutexRelease(i2c_Mutex);
	#elif USE_FREERTOS
	xSemaphoreGive(m_i2c_mutex);
	#endif
	
	return true;
}


/**************************************************************
 * @brief  Function gets a message queue handle.
 * @notes  None.
 * @param  void
 * @return void
 **************************************************************/
#if USE_RTX5
static osMessageQueueId_t os_get_queue_handle(etMessageQueues queue) {
#elif USE_FREERTOS
static QueueHandle_t os_get_queue_handle(etMessageQueues queue) {
#endif	
	if (queue == MEDIATOR_QUEUE) {
		#if USE_RTX5
		return mediator_MsgQueue_id;
		#elif USE_FREERTOS
		return m_mediator_queue;
		#endif
	}
	else if (queue == SENSOR_A_QUEUE) {
		#if USE_RTX5
		return sensorA_MsgQueue_id;
		#elif USE_FREERTOS
		return m_sensorA_queue;
		#endif
	}
	else if (queue == SENSOR_B_QUEUE) {
		#if USE_RTX5
		return sensorB_MsgQueue_id;
		#elif USE_FREERTOS
		return m_sensorB_queue;
		#endif
	}
	else if (queue == SENSOR_C_QUEUE) {
		#if USE_RTX5
		return sensorC_MsgQueue_id;
		#elif USE_FREERTOS
		return m_sensorC_queue;
		#endif
	}
	else if (queue == SENSOR_D_QUEUE) {
		#if USE_RTX5
		return sensorD_MsgQueue_id;
		#elif USE_FREERTOS
		return m_sensorD_queue;
		#endif
	}
	else {
		return NULL;
	}
}


/**************************************************************
 * @brief  Function gets a task handle.
 * @notes  None.
 * @param  void
 * @return void
 **************************************************************/
static TaskHandle_t os_get_task_handle(etMessageQueues queue) {
	if (queue == MEDIATOR_QUEUE) {
		return m_mediator_thread;
	}
	else if (queue == SENSOR_A_QUEUE) {
		return m_sensorA_thread;
	}
	else if (queue == SENSOR_B_QUEUE) {
		return m_sensorB_thread;
	}
	else if (queue == SENSOR_C_QUEUE) {
		return m_sensorC_thread;
	}
	else if (queue == SENSOR_D_QUEUE) {
		return m_sensorD_thread;
	}
	else {
		return NULL;
	}
}


/**************************************************************
 * @brief  Function puts a message onto a message queue.
 * @notes  None.
 * @param  void
 * @return void
 **************************************************************/
bool os_message_queue_put(etMessageQueues queue, MSGQUEUE_OBJ_t *msg, bool fromISR) {
#if USE_RTX5
	osStatus_t status_OS = osMessageQueuePut(os_get_queue_handle(queue), msg, osPriorityNone, osWaitForever);
	if (status_OS == osOK) {
		return true;
	}
	else {
		return false;
	}
#elif USE_FREERTOS
	BaseType_t status_OS;
	if (fromISR) {
		BaseType_t xHigherPriorityTaskWoken = pdFALSE;
		status_OS = xQueueSendToBackFromISR(os_get_queue_handle(queue), msg, &xHigherPriorityTaskWoken);
		if (status_OS == pdTRUE) {
			vTaskNotifyGiveFromISR(os_get_task_handle(queue), &xHigherPriorityTaskWoken);
			//status_OS = xTaskNotifyFromISR(os_get_task_handle(queue), MESSAGE_WAITING_NOTIFICATION, eSetValueWithOverwrite , &xHigherPriorityTaskWoken);
		}
		portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
	}
	else {
		status_OS = xQueueSendToBack(os_get_queue_handle(queue), msg, portMAX_DELAY);
		if (status_OS == pdTRUE) {
			status_OS = xTaskNotifyGive(os_get_task_handle(queue));
			//status_OS = xTaskNotify(os_get_task_handle(queue), MESSAGE_WAITING_NOTIFICATION, eSetValueWithOverwrite);
		}
		portYIELD();
	}
	if (status_OS == pdTRUE) {
		return true;
	}
	else {
		return false;
	}
#endif
}


/**************************************************************
 * @brief  Function puts a message onto the Sensor A message queue.
 * @notes  None.
 * @param  void
 * @return void
 **************************************************************/
bool os_message_queue_get(etMessageQueues queue, MSGQUEUE_OBJ_t *msg, bool fromISR) {	
#if USE_RTX5
	osStatus_t status_OS = osMessageQueueGet(os_get_queue_handle(queue), msg, NULL, MSG_QUEUE_BLOCK_MS);
	if (status_OS == osOK) {
		return true;
	}
	else {
		return false;
	}
#elif USE_FREERTOS	
	BaseType_t status_OS;
	
	if (fromISR) {
		BaseType_t xHigherPriorityTaskWoken = pdFALSE;
		status_OS = xQueueReceiveFromISR(os_get_queue_handle(queue), msg, &xHigherPriorityTaskWoken);
		portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
	}
	else {
		ulTaskNotifyTake(NOTIFICATION_DEFAULT_VALUE, portMAX_DELAY);
		status_OS = xQueueReceive(os_get_queue_handle(queue), msg, MSG_QUEUE_BLOCK_MS);
	}
	if (status_OS == pdTRUE) {
		return true;
	}
	else {
		return false;
	}
#endif
}


/**************************************************************
 * @brief  Function checks for stack overflow events.
 * @notes  None.
 * @param  void
 * @return void
 **************************************************************/
#if USE_FREERTOS
void vApplicationStackOverflowHook(TaskHandle_t xTask, signed char *pcTaskName) {
	NRF_LOG_INFO("Stack overflow!");
	app_error_fault_handler(NULL, NULL, NULL);
}
#endif

/**************************************************************
 * @brief  Application logging thread.
 * @notes  Thread handles log events.
 * @param  void
 * @return void
 **************************************************************/
void app_thread_logger(void *argument) {
	UNUSED_PARAMETER(argument);
	
	NRF_LOG_INFO("Logger thread start!");
	
	while (1) {
		NRF_LOG_INFO("Logger thread running!");
		
		NRF_LOG_FLUSH();
		
		NRF_LOG_INFO("Logger thread sleeping!");
		
		os_thread_yield();
	}
}


/**************************************************************
 * @brief  Application logging thread hook.
 * @notes  Thread handles log events.
 * @param  void
 * @return void
 **************************************************************/
#if (NRF_LOG_ENABLED && NRF_LOG_DEFERRED)
void log_pending_hook(void) {
	BaseType_t result = pdFAIL;

    if (__get_IPSR() != 0) {
        BaseType_t higherPriorityTaskWoken = pdFALSE;
        result = xTaskNotifyFromISR(m_logger_thread, 0, eSetValueWithoutOverwrite, &higherPriorityTaskWoken);
        if (result != pdFAIL) {
			portYIELD_FROM_ISR(higherPriorityTaskWoken);
        }
    }
    else {
        UNUSED_RETURN_VALUE(xTaskNotify(m_logger_thread, 0, eSetValueWithoutOverwrite));
    }
 }
#endif


/**************************************************************
 * @brief  Application mediator thread.
 * @notes  Thread handles sensor threads by waiting for interrupt-driven configuration events.
 *		   It also handles all data storage (both configuration and sensor data) in both RAM and FLASH.
 * @param  void
 * @return void
 **************************************************************/
void app_thread_mediator(void *argument) {
	UNUSED_PARAMETER(argument);
	static MSGQUEUE_OBJ_t msg;
	
	NRF_LOG_INFO("Mediator thread start!");
	
	// Power on the sensors by default.
	#if (USE_SENSOR_POWER_CTRL == false)
	sensor_power_control(true);
	#endif
	
	NRF_LOG_INFO("Free Heap: %d", xPortGetFreeHeapSize());
	
	// Send a startup message to the mediator thread.
	msg.Idx = INIT;
	os_message_queue_put(MEDIATOR_QUEUE, &msg, false);
	
	while (1) {
		NRF_LOG_INFO("Mediator thread running!");
		
		// Wait from a message from the BLE characteristic ISR (start/stop/clear) or each sensor thread.
		if (os_message_queue_get(MEDIATOR_QUEUE, &msg, false)) {
			NRF_LOG_INFO("Message received (Mediator)!");
			
			// The system has just started up.
			if (msg.Idx == INIT) {
				NRF_LOG_INFO("Initialising (Mediator)!");
				
				// Update the current system state.
				set_system_state(STANDBY);
				
				// Power off the sensors in standby.
				#if (USE_SENSOR_POWER_CTRL == true)
				sensor_power_control(false);
				#endif
			}
			// A start request has occurred.
			else if (msg.Idx == START) {
				NRF_LOG_INFO("Starting (Mediator)!");
				
				// Start measurements.
				if (get_system_state() != MEASURING) {
					// Update the current system state.
					set_system_state(MEASURING);
					
					// Clear measurement data.
					flash_erase_pages();
					flash_clear_measurements();
						
					// Implement the startup delay timer as required.
					delay_ms(get_start_delay_ms());
						
					// Power on the sensors during measurement.
					#if (USE_SENSOR_POWER_CTRL == true)
					sensor_power_control(true);
					#endif
						
					// Shutdown sensor thread A or setup sensor interrupts as required.
					if (!shtc3_info.enabled) {
						msg.Idx = STOP;
						os_message_queue_put(SENSOR_A_QUEUE, &msg, false);
					}
					else {
						if (shtc3_info.mode) {
							msg.Idx = STOP_INTERRUPTS;
						}
						else {
							msg.Idx = START_INTERRUPTS;
						}
						os_message_queue_put(SENSOR_A_QUEUE, &msg, false);
					}
						
					// Shutdown sensor thread B or setup sensor interrupts as required.
					if (!ms5607_info.enabled) {
						msg.Idx = STOP;
						os_message_queue_put(SENSOR_B_QUEUE, &msg, false);
					}
					else {
						if (ms5607_info.mode) {
							msg.Idx = STOP_INTERRUPTS;
						}
						else {
							msg.Idx = START_INTERRUPTS;
						}
						os_message_queue_put(SENSOR_B_QUEUE, &msg, false);
					}
						
					// Shutdown sensor thread C or setup sensor interrupts as required.
					if (!tsl2x7x_info.enabled) {
						msg.Idx = STOP;
						os_message_queue_put(SENSOR_C_QUEUE, &msg, false);
					}
					else {
						if (tsl2x7x_info.mode) {
							msg.Idx = STOP_INTERRUPTS;
						}
						else {
							msg.Idx = START_INTERRUPTS;
						}
						os_message_queue_put(SENSOR_C_QUEUE, &msg, false);
					}
						
					// Shutdown sensor thread D or setup sensor interrupts as required.
					if (!mxc400x_info.enabled) {
						msg.Idx = STOP;
						os_message_queue_put(SENSOR_D_QUEUE, &msg, false);
					}
					else {
						if (mxc400x_info.mode) {
							msg.Idx = STOP_INTERRUPTS;
						}
						else {
							msg.Idx = START_INTERRUPTS;
						}
						os_message_queue_put(SENSOR_D_QUEUE, &msg, false);
					}
					
					// Start the common poll timer, which will wake enabled sensor threads as required.
					os_poll_timer_start(get_common_poll_period_ms(), false);
				}
				else {
					NRF_LOG_INFO("System start request ignored!");
				}
			}
			// A stop request has occurred.
			else if (msg.Idx == STOP) {
				NRF_LOG_INFO("Stopping (Mediator)!");
				
				// Halt the system.
				if (get_system_state() == MEASURING) {
					// Update the current system state.
					set_system_state(STANDBY);
					
					// Stop the common poll timer.
					os_poll_timer_stop(false);
						
					// Shutdown all sensor threads.
					msg.Idx = STOP;
					os_message_queue_put(SENSOR_A_QUEUE, &msg, false);
					os_message_queue_put(SENSOR_B_QUEUE, &msg, false);
					os_message_queue_put(SENSOR_C_QUEUE, &msg, false);
					os_message_queue_put(SENSOR_D_QUEUE, &msg, false);
						
					// Wait for sensor threads to finish.
					while((shtc3_info.status == POWERED_ON) || (ms5607_info.status == POWERED_ON) || (tsl2x7x_info.status == POWERED_ON) || (mxc400x_info.status == POWERED_ON));
						
					// Power off the sensors in standby.
					#if (USE_SENSOR_POWER_CTRL == true)
					sensor_power_control(false);
					#endif
				}
				else {
					NRF_LOG_INFO("System stop request ignored!");
				}
			}
			// A shutdown request has occurred.
			else if (msg.Idx == SHUTDOWN) {
				NRF_LOG_INFO("Shutting down (Mediator)!");
				
				// Prepare the system for shutdown.
				if (get_system_state() != MEASURING) {
					// Update the current system state.
					set_system_state(SHUTTING_DOWN);
					
					// Wait for sensor threads to finish.
					while((shtc3_info.status == POWERED_ON) || (ms5607_info.status == POWERED_ON) || (tsl2x7x_info.status == POWERED_ON) || (mxc400x_info.status == POWERED_ON));
					
					// Power off the sensors during shutdown.
					#if (USE_SENSOR_POWER_CTRL == true)
					sensor_power_control(false);
					#endif
					
					flash_deinit();
					
					ble_disconnect(false);
					
					board_deinit();
					
					sleep_mode_enter();
				}
				else {
					NRF_LOG_INFO("System shutdown request ignored!");
				}
			}
			// A clear data request has occurred.
			else if (msg.Idx == CLEAR_DATA) {
				NRF_LOG_INFO("Clearing data (Mediator)!");
				
				// Clear all stored data.
				if (get_system_state() != MEASURING) {
					flash_clear_measurements();
				}
				else {
					NRF_LOG_INFO("Clear data request ignored!");
				}
			}
			// A send data request has occurred.
			else if (msg.Idx == SEND_DATA) {
				NRF_LOG_INFO("Sending data (Mediator)!");
				
				while(!ble_send_label_data());
			}
			// Sensor data has been received, and needs to be stored.
			else if ((msg.Idx == SENSOR_A_DATA) || (msg.Idx == SENSOR_B_DATA) || (msg.Idx == SENSOR_C_DATA) || (msg.Idx == SENSOR_D_DATA)) {
				NRF_LOG_INFO("Storing data (Mediator)!");
				
				store_measurement(msg.timestamp_unix, msg.timestamp_ms, msg.Idx, (int16_t*)msg.sensor_channel_data);
				increment_num_measurements();
				
				// Halt when measurement is complete.
				if (is_meas_complete()) {
					set_system_state(COMPLETE);
					
					NRF_LOG_INFO("Measurement complete!");
					os_poll_timer_stop(false);
					
					// If disconnected, advertise.
					if (getConnectionStatus() == BLE_DISCONNECTED) {
						advertising_start(false);
					}
				}
			}
		}
		
		NRF_LOG_INFO("Mediator thread sleeping!");
		
		os_thread_yield();
	}
}


/**************************************************************
 * @brief  Application sensor thread (sensor A - SHTC3).
 * @notes  Thread handles sensor configuration and communications as required, then relays data back to the mediator thread.
 * @param  void
 * @return void
 **************************************************************/
void app_thread_sensorA(void *argument) {
	UNUSED_PARAMETER(argument);
	MSGQUEUE_OBJ_t msg;
	etError status_sensor;
	float temp, humidity;
	
	NRF_LOG_INFO("Sensor A thread start!");
	
	while (1) {
		// Wait from a message from the poll timer ISR (start) or the mediator (start/stop).
		if (os_message_queue_get(SENSOR_A_QUEUE, &msg, false)) {
			NRF_LOG_INFO("Message received (Sensor A)!");
			
			// Read data from the sensor.
			if ((msg.Idx == START) && (shtc3_info.enabled)) {
				NRF_LOG_INFO("Reading data (Sensor A)!");
				
				// Ensure that the sensor is powered on.				
				if (shtc3_info.status != POWERED_ON) {
					if (os_acquire_i2c_mutex()) {
						status_sensor = SHTC3_Wakeup();
						os_release_i2c_mutex();
						if (status_sensor == NO_ERROR) {
							NRF_LOG_INFO("SHTC3 sensor initialised successfully.");
							shtc3_info.status = POWERED_ON;
						}
						else {
							NRF_LOG_INFO("SHTC3 sensor initialisation failed!");
							shtc3_info.status = UNKNOWN;
						}
					}
				}
			
				// Read sensor data as required.
				if (shtc3_info.status == POWERED_ON) {
					if (os_acquire_i2c_mutex()) {
						status_sensor = (etError)read_SHTC3(&temp, &humidity);
						os_release_i2c_mutex();
						if (status_sensor) {
							NRF_LOG_INFO("SHTC3 sensor read OK.");
							NRF_LOG_INFO("Temperature = " NRF_LOG_FLOAT_MARKER " C", NRF_LOG_FLOAT(temp));
							NRF_LOG_INFO("Humidity = " NRF_LOG_FLOAT_MARKER " %rH", NRF_LOG_FLOAT(humidity));
							
							// Communicate data to the mediator thread for storage.
							msg.Idx = SENSOR_A_DATA;
							msg.timestamp_unix = get_system_time_unix();
							msg.timestamp_ms = get_system_time_ms();
							msg.sensor_channel_data[0] = (int16_t)(temp * 10);
							msg.sensor_channel_data[1] = (int16_t)(humidity * 10);
							os_message_queue_put(MEDIATOR_QUEUE, &msg, false);
						}
						else {
							NRF_LOG_INFO("SHTC3 sensor read failed!");
						}						
					}
				}
			}
			// Configure the sensor for interrupt-driven operation, if supported.
			else if (((msg.Idx == START_INTERRUPTS) || (msg.Idx == STOP_INTERRUPTS)) && (!shtc3_info.mode)) {
				NRF_LOG_INFO("Configuring interrupts (Sensor A)!");
				
				// Interrupts not supported - shut down the sensor.
				NRF_LOG_INFO("SHTC3 does not support interrupts, shutting down!");
				// Switch the sensor off.
				if (shtc3_info.status != POWERED_OFF) {	
					if (os_acquire_i2c_mutex()) {
						status_sensor = SHTC3_Sleep();
						os_release_i2c_mutex();
						if (status_sensor != NO_ERROR) {
							NRF_LOG_INFO("SHTC3 sensor shutdown succesfully.");
							shtc3_info.status = POWERED_OFF;
						}
						else {
							NRF_LOG_INFO("SHTC3 sensor shutdown failed!");
							shtc3_info.status = UNKNOWN;
						}
					}
				}
			}
			// Shut down the sensor.
			else if (msg.Idx == STOP) {
				NRF_LOG_INFO("Shutting down (Sensor A)!");
				
				// Switch the sensor off.
				if (shtc3_info.status != POWERED_OFF) {	
					if (os_acquire_i2c_mutex()) {
						status_sensor = SHTC3_Sleep();
						os_release_i2c_mutex();
						if (status_sensor != NO_ERROR) {
							NRF_LOG_INFO("SHTC3 sensor shutdown succesfully.");
							shtc3_info.status = POWERED_OFF;
						}
						else {
							NRF_LOG_INFO("SHTC3 sensor shutdown failed!");
							shtc3_info.status = UNKNOWN;
						}
					}
				}
			}
			// Message not handled.
			else {
				NRF_LOG_INFO("Sensor A command ignored!");
			}
		}
		os_thread_yield();
	}
}


/**************************************************************
 * @brief  Application sensor thread (sensor B - MS5607).
 * @notes  Thread handles sensor configuration and communications as required, then relays data back to the mediator thread.
 * @param  void
 * @return void
 **************************************************************/
void app_thread_sensorB(void *argument) {
	UNUSED_PARAMETER(argument);
	MSGQUEUE_OBJ_t msg;
	bool status_sensor;
	float temperature, pressure, altitude;
	
	NRF_LOG_INFO("Sensor B thread start!");
 
	while (1) {
		// Wait from a message from the poll timer ISR (start) or the mediator (start/stop).
		if (os_message_queue_get(SENSOR_B_QUEUE, &msg, false)) {
			NRF_LOG_INFO("Message received (Sensor B)!");
			
			// Read data from the sensor.
			if ((msg.Idx == START) && (ms5607_info.enabled)) {
				NRF_LOG_INFO("Reading data (Sensor B)!");
				
				// Ensure that the sensor is powered on.
				if (ms5607_info.status != POWERED_ON) {
					if (os_acquire_i2c_mutex()) {
						status_sensor = MS5607_Initialise();
						os_release_i2c_mutex();
						if (status_sensor) {
							NRF_LOG_INFO("MS5607 sensor initialised successfully.");
							ms5607_info.status = POWERED_ON;
						}
						else {
							NRF_LOG_INFO("MS5607 sensor initialisation failed!");
							ms5607_info.status = UNKNOWN;
						}
					}
				}
				
				// Read sensor data as required.
				if (ms5607_info.status == POWERED_ON) {
					if (os_acquire_i2c_mutex()) {
						status_sensor = read_MS5607(&temperature, &pressure, &altitude);
						os_release_i2c_mutex();
						if (status_sensor) {
							NRF_LOG_INFO("MS5607 read OK.");
							NRF_LOG_INFO("Temperature = " NRF_LOG_FLOAT_MARKER " C", NRF_LOG_FLOAT(temperature));
							NRF_LOG_INFO("Pressure = " NRF_LOG_FLOAT_MARKER " mbar", NRF_LOG_FLOAT(pressure));
							NRF_LOG_INFO("Altitude = " NRF_LOG_FLOAT_MARKER " m", NRF_LOG_FLOAT(altitude));
							
							// Communicate data to the mediator thread for storage.
							msg.Idx = SENSOR_B_DATA;
							msg.timestamp_unix = get_system_time_unix();
							msg.timestamp_ms = get_system_time_ms();
							msg.sensor_channel_data[0] = (int16_t)(temperature * 10);
							msg.sensor_channel_data[1] = (int16_t)(pressure * 10);
							msg.sensor_channel_data[2] = (int16_t)(altitude * 10);
							os_message_queue_put(MEDIATOR_QUEUE, &msg, false);
						}
						else {
							NRF_LOG_INFO("MS5607 sensor read failed!");
						}
					}
				}
			}
			// Configure the sensor for interrupt-driven operation, if supported.
			else if ((msg.Idx == START_INTERRUPTS) && (!ms5607_info.mode)) {
				NRF_LOG_INFO("Configuring interrupts (Sensor B)!");
				
				// Interrupts not supported - shut down the sensor.
				NRF_LOG_INFO("MS5607 sensor does not support interrupts, shutting down!");
				// Switch the sensor off.
				if (ms5607_info.status != POWERED_OFF) {
					NRF_LOG_INFO("MS5607 sensor does not support shutdown!");
					ms5607_info.status = POWERED_OFF;
				}
			}
			// Switch off interrupts, if supported.
			else if (msg.Idx == STOP_INTERRUPTS) {
				NRF_LOG_INFO("Configuring interrupts (Sensor B)!");
				
				// Interrupts not supported - shut down the sensor.
				NRF_LOG_INFO("MS5607 sensor does not support interrupts, shutting down!");
				// Switch the sensor off.
				if (ms5607_info.status != POWERED_OFF) {
					NRF_LOG_INFO("MS5607 sensor does not support shutdown!");
					ms5607_info.status = POWERED_OFF;
				}
			}
			// Shut down the sensor.
			else if (msg.Idx == STOP) {
				NRF_LOG_INFO("Shutting down (Sensor B)!");
				
				// Switch the sensor off.
				if (ms5607_info.status != POWERED_OFF) {
					NRF_LOG_INFO("MS5607 sensor does not support shutdown!");
					ms5607_info.status = POWERED_OFF;
				}
			}
			// Message not handled.
			else {
				NRF_LOG_INFO("Sensor B command ignored!");
			}
		}
		os_thread_yield();
	}
}


/**************************************************************
 * @brief  Application sensor thread (sensor C - TSL2X7X).
 * @notes  Thread handles sensor configuration and communications as required, then relays data back to the mediator thread.
 * @param  void
 * @return void
 **************************************************************/
void app_thread_sensorC(void *argument) {
	UNUSED_PARAMETER(argument);
	MSGQUEUE_OBJ_t msg;
	etError status_sensor;
	bool luxOK = false, proxOK = false;
	int lux_data, prox_data;
	struct iio_chan_spec chan;
	
	NRF_LOG_INFO("Sensor C thread start!");
 
	while (1) {
		// Wait from a message from the poll timer ISR (start) or the mediator (start/stop).
		if (os_message_queue_get(SENSOR_C_QUEUE, &msg, false)) {
			NRF_LOG_INFO("Message received (Sensor C)!");
			
			// Read data from the sensor.
			if ((msg.Idx == START) && (tsl2x7x_info.enabled)) {
				NRF_LOG_INFO("Reading data (Sensor C)!");
				
				// Ensure that the sensor is powered on.
				if (tsl2x7x_info.status != POWERED_ON) {
					if (os_acquire_i2c_mutex()) {
						status_sensor = (etError)tsl2x7x_initialise(tsl2771, &tsl2X7X_chip_global);
						os_release_i2c_mutex();
						if (status_sensor == NO_ERROR) {
							NRF_LOG_INFO("TSL2X7X sensor initialised successfully.");
							tsl2x7x_info.status = POWERED_ON;
						}
						else {
							NRF_LOG_INFO("TSL2X7X sensor initialisation failed!");
							tsl2x7x_info.status = UNKNOWN;
						}
					}
				}
							
				// Read sensor data as required.
				if (tsl2x7x_info.status == POWERED_ON) {
					if (os_acquire_i2c_mutex()) {
						lux_data = tsl2x7x_get_lux(&tsl2X7X_chip_global);
						os_release_i2c_mutex();
						if (lux_data >= 0) {
							luxOK = true;
						}
					}
					if (os_acquire_i2c_mutex()) {
						prox_data = tsl2x7x_get_prox(&tsl2X7X_chip_global);	
						os_release_i2c_mutex();
						if (prox_data >= 0) {
							proxOK = true;
						}
					}
				}
				if (luxOK && proxOK) {
					NRF_LOG_INFO("TSL2771 read OK.");
					NRF_LOG_INFO("Lux = %d", lux_data);
					NRF_LOG_INFO("Proximity = %d", prox_data);
					
					// Communicate data to the mediator thread for storage.
					msg.Idx = SENSOR_C_DATA;
					msg.timestamp_unix = get_system_time_unix();
					msg.timestamp_ms = get_system_time_ms();
					msg.sensor_channel_data[0] = (int16_t)(lux_data * 10);
					msg.sensor_channel_data[1] = (int16_t)(prox_data * 10);
					os_message_queue_put(MEDIATOR_QUEUE, &msg, false);
				}
				else {
					NRF_LOG_INFO("TSL2771 read failed!");
				}
			}
			// Configure the sensor for interrupt-driven operation, if supported.
			else if ((msg.Idx == START_INTERRUPTS) && (!tsl2x7x_info.mode)) {
				NRF_LOG_INFO("Configuring interrupts (Sensor C)!");
				
				NRF_LOG_INFO("Setting up interrupts on TSL2771.");
#if (TSLX7X_DEF_INTERRUPTS_EN)
				// Configure intensity thresholds as required.
				#if (TSLX7X_DEF_INTERRUPTS_EN && 0x10)
				// Set the intensity high threshold.
				bool alsSettingsOK = false;
				chan.type = IIO_INTENSITY;
				if (os_acquire_i2c_mutex()) {
					status_sensor = (etError)tsl2x7x_write_thresh(&tsl2X7X_chip_global, &chan, IIO_EV_TYPE_THRESH, IIO_EV_DIR_RISING, TSLX7X_DEF_ALS_THRES_HI);
					os_release_i2c_mutex();
					if (status_sensor == NO_ERROR) {
						// Set the proximity high threshold.
						chan.type = IIO_PROXIMITY;
						if (os_acquire_i2c_mutex()) {
							status_sensor = (etError)tsl2x7x_write_thresh(&tsl2X7X_chip_global, &chan, IIO_EV_TYPE_THRESH, IIO_EV_DIR_FALLING, TSLX7X_DEF_ALS_THRES_LOW);
							os_release_i2c_mutex();
							if (status_sensor == NO_ERROR) {
								if (os_acquire_i2c_mutex()) {
									status_sensor = (etError)tsl2x7x_write_interrupt_config(&tsl2X7X_chip_global, &chan, true);
									os_release_i2c_mutex();
									if (status_sensor == NO_ERROR) {
										alsSettingsOK = true;
									}
								}
							}
						}
					}
				}
				if (alsSettingsOK) {
					NRF_LOG_INFO("TSL2X7X sensor intensity interrupt configuration set successfully.");
				}
				else {
					NRF_LOG_INFO("TSL2X7X sensor intensity interrupt configuration failed!");
				}
				#endif
				// Configure proximity thresholds as required.
				#if (TSLX7X_DEF_INTERRUPTS_EN && 0x20)
				// Set the proximity high threshold.
				bool prxSettingsOK = false;
				chan.type = IIO_PROXIMITY;
				if (os_acquire_i2c_mutex()) {
					status_sensor = (etError)tsl2x7x_write_thresh(&tsl2X7X_chip_global, &chan, IIO_EV_TYPE_THRESH, IIO_EV_DIR_RISING, TSLX7X_DEF_PRX_THRES_HI);
					os_release_i2c_mutex();
					if (status_sensor == NO_ERROR) {
						// Set the proximity high threshold.
						chan.type = IIO_PROXIMITY;
						if (os_acquire_i2c_mutex()) {
							status_sensor = (etError)tsl2x7x_write_thresh(&tsl2X7X_chip_global, &chan, IIO_EV_TYPE_THRESH, IIO_EV_DIR_FALLING, TSLX7X_DEF_PRX_THRES_LOW);
							os_release_i2c_mutex();
							if (status_sensor == NO_ERROR) {
								if (os_acquire_i2c_mutex()) {
									status_sensor = (etError)tsl2x7x_write_interrupt_config(&tsl2X7X_chip_global, &chan, true);
									os_release_i2c_mutex();
									if (status_sensor == NO_ERROR) {
										prxSettingsOK = true;
									}
								}
							}
						}
					}
				}
				if (prxSettingsOK) {
					NRF_LOG_INFO("TSL2X7X sensor proximity interrupt configuration set successfully.");
				}
				else {
					NRF_LOG_INFO("TSL2X7X sensor proximity interrupt configuration failed!");
				}
				#endif
				// Set up interrupt handling on the MCU.
				nrf_drv_gpiote_in_event_enable(PIN_INTERRUPT_TSL2X7X, true);
#else
				// Interrupts not enabled - shut down the sensor.
				NRF_LOG_INFO("TSL2X7X interrupts not enabled, shutting down!");
				// Switch the sensor off.
				if (tsl2x7x_info.status != POWERED_OFF) {
					if (os_acquire_i2c_mutex()) {
						status_sensor = (etError)tsl2x7x_chip_off(&tsl2X7X_chip_global);
						os_release_i2c_mutex();
						if (status_sensor != NO_ERROR) {
							tsl2x7x_info.status = POWERED_OFF;
							NRF_LOG_INFO("TSL2X7X sensor shutdown succesfully.");
						}
						else {
							tsl2x7x_info.status = UNKNOWN;
							NRF_LOG_INFO("TSL2X7X sensor shutdown failed!");
						}
					}
				}
#endif
			}
			// Switch off interrupts, if supported.
			else if (msg.Idx == STOP_INTERRUPTS) {
				NRF_LOG_INFO("Configuring interrupts (Sensor C)!");
				
				NRF_LOG_INFO("Halting interrupts on TSL2771.");
				
				// Disable intensity interrupts.
				bool alsSettingsOK = false;
				chan.type = IIO_INTENSITY;
				if (os_acquire_i2c_mutex()) {
					status_sensor = (etError)tsl2x7x_write_interrupt_config(&tsl2X7X_chip_global, &chan, true);
					os_release_i2c_mutex();
					if (status_sensor == NO_ERROR) {
						alsSettingsOK = true;
					}
				}
				
				// Disable proximity interrupts.
				bool prxSettingsOK = false;
				chan.type = IIO_PROXIMITY;
				if (os_acquire_i2c_mutex()) {
					status_sensor = (etError)tsl2x7x_write_interrupt_config(&tsl2X7X_chip_global, &chan, true);
					os_release_i2c_mutex();
					if (status_sensor == NO_ERROR) {
						prxSettingsOK = true;
					}
				}
				
				if (prxSettingsOK && alsSettingsOK) {
					NRF_LOG_INFO("TSL2X7X sensor interrupt configuration disabled successfully.");
				}
				else {
					NRF_LOG_INFO("TSL2X7X sensor interrupt configuration failed!");
				}
				
				// Set up interrupt handling on the MCU.
				nrf_drv_gpiote_in_event_disable(PIN_INTERRUPT_TSL2X7X);
			}
			// Shut down the sensor.
			else if (msg.Idx == STOP) {
				NRF_LOG_INFO("Shutting down (Sensor C)!");
				
				// Switch the sensor off.
				if (tsl2x7x_info.status != POWERED_OFF) {
					if (os_acquire_i2c_mutex()) {
						status_sensor = (etError)tsl2x7x_chip_off(&tsl2X7X_chip_global);
						os_release_i2c_mutex();
						if (status_sensor != NO_ERROR) {
							tsl2x7x_info.status = POWERED_OFF;
							NRF_LOG_INFO("TSL2X7X sensor shutdown succesfully.");
						}
						else {
							tsl2x7x_info.status = UNKNOWN;
							NRF_LOG_INFO("TSL2X7X sensor shutdown failed!");
						}
					}
				}
			}
			// Message not handled.
			else {
				NRF_LOG_INFO("Sensor C command ignored!");
			}
		}
		os_thread_yield();
	}
}


/**************************************************************
 * @brief  Application sensor thread (sensor D - MXC400X).
 * @notes  Thread handles sensor configuration and communications as required, then relays data back to the mediator thread.
 * @param  void
 * @return void
 **************************************************************/
void app_thread_sensorD(void *argument) {
	UNUSED_PARAMETER(argument);
	MSGQUEUE_OBJ_t msg;
	etError status_sensor;
	bool accOK = false, tempOK = false;
	
	NRF_LOG_INFO("Sensor D thread start!");
 
	while (1) {
		// Wait from a message from the poll timer ISR (start) or the mediator (start/stop).
		if (os_message_queue_get(SENSOR_D_QUEUE, &msg, false)) {
			NRF_LOG_INFO("Message received (Sensor D)!");
			
			// Read data from the sensor.
			if ((msg.Idx == START) && (mxc400x_info.enabled)) {
				NRF_LOG_INFO("Reading data (Sensor D)!");
				
				// Ensure that the sensor is powered on.
				if (mxc400x_info.status != POWERED_ON) {
					if (os_acquire_i2c_mutex()) {		
						status_sensor = mxc400x_chip_init(&mxc400x_data_global, MXC400X_CAL_OFFSET_X, MXC400X_CAL_OFFSET_Y, MXC400X_CAL_OFFSET_Z, false);
						os_release_i2c_mutex();
						if (status_sensor == NO_ERROR) {
							NRF_LOG_INFO("MXC400X sensor initialised successfully with range %d.", MXC400X_SEL_RANGE);
							mxc400x_info.status = POWERED_ON;
						}
						else {
							NRF_LOG_INFO("MXC400X sensor initialisation failed!");
							mxc400x_info.status = UNKNOWN;
						}
					}
				}
							
				// Read sensor data as required.
				if (mxc400x_info.status == POWERED_ON) {
					if (os_acquire_i2c_mutex()) {	
						accOK = (mxc400x_read_xyz(&mxc400x_data_global) == NO_ERROR);
						tempOK = (mxc400x_read_temp(&mxc400x_data_global) == NO_ERROR);
						os_release_i2c_mutex();
						if (accOK && tempOK) {
							NRF_LOG_INFO("MXC400X read OK.");
							NRF_LOG_INFO("X = " NRF_LOG_FLOAT_MARKER " g", NRF_LOG_FLOAT(mxc400x_data_global.chans_calc[MXC400X_AXIS_X]));
							NRF_LOG_INFO("Y = " NRF_LOG_FLOAT_MARKER " g", NRF_LOG_FLOAT(mxc400x_data_global.chans_calc[MXC400X_AXIS_Y]));
							NRF_LOG_INFO("Z = " NRF_LOG_FLOAT_MARKER " g", NRF_LOG_FLOAT(mxc400x_data_global.chans_calc[MXC400X_AXIS_Z]));
							NRF_LOG_INFO("Temperature = " NRF_LOG_FLOAT_MARKER " C", NRF_LOG_FLOAT(mxc400x_data_global.temp));
							
							// Communicate data to the mediator thread for storage.
							msg.Idx = SENSOR_D_DATA;
							msg.timestamp_unix = get_system_time_unix();
							msg.timestamp_ms = get_system_time_ms();
							msg.sensor_channel_data[0] = (int16_t)(mxc400x_data_global.chans_calc[MXC400X_AXIS_X] * 10);
							msg.sensor_channel_data[1] = (int16_t)(mxc400x_data_global.chans_calc[MXC400X_AXIS_Y] * 10);
							msg.sensor_channel_data[2] = (int16_t)(mxc400x_data_global.chans_calc[MXC400X_AXIS_Z] * 10);
							msg.sensor_channel_data[3] = (int16_t)(mxc400x_data_global.temp * 10);
							os_message_queue_put(MEDIATOR_QUEUE, &msg, false);
						}
						else {
							NRF_LOG_INFO("MXC400X read failed!");
						}
					}
				}
			}
			// Configure the sensor for interrupt-driven operation, if supported.
			else if ((msg.Idx == START_INTERRUPTS) && (!mxc400x_info.mode)) {
				NRF_LOG_INFO("Configuring interrupts (Sensor D)!");
				
				NRF_LOG_INFO("Setting up interrupts on MXC400X.");
				
				if (os_acquire_i2c_mutex()) {		
					status_sensor = mxc400x_chip_init(&mxc400x_data_global, MXC400X_CAL_OFFSET_X, MXC400X_CAL_OFFSET_Y, MXC400X_CAL_OFFSET_Z, true);
					os_release_i2c_mutex();
					if (status_sensor == NO_ERROR) {
						NRF_LOG_INFO("MXC400X sensor initialised successfully with range %d.", MXC400X_SEL_RANGE);
						mxc400x_info.status = POWERED_ON;
					}
					else {
						NRF_LOG_INFO("MXC400X sensor initialisation failed!");
						mxc400x_info.status = UNKNOWN;
					}
				}
				
				// Set up interrupt handling on the MCU.
				nrf_drv_gpiote_in_event_enable(PIN_INTERRUPT_MXC400X, true);
				
			}
			// Switch off interrupts, if supported.
			else if (msg.Idx == STOP_INTERRUPTS) {
				NRF_LOG_INFO("Configuring interrupts (Sensor D)!");
				
				NRF_LOG_INFO("Halting interrupts on MXC400X.");
				
				if (os_acquire_i2c_mutex()) {		
					status_sensor = mxc400x_chip_init(&mxc400x_data_global, 0, 0, 0, false);
					os_release_i2c_mutex();
					if (status_sensor == NO_ERROR) {
						NRF_LOG_INFO("MXC400X sensor initialised successfully with range %d.", MXC400X_SEL_RANGE);
						mxc400x_info.status = POWERED_ON;
					}
					else {
						NRF_LOG_INFO("MXC400X sensor initialisation failed!");
						mxc400x_info.status = UNKNOWN;
					}
				}
				
				// Set up interrupt handling on the MCU.
				nrf_drv_gpiote_in_event_disable(PIN_INTERRUPT_MXC400X);
			}
			// Shut down the sensor.
			else if (msg.Idx == STOP) {
				NRF_LOG_INFO("Shutting down (Sensor D)!");
				
				// Switch the sensor off.
				if (mxc400x_info.status != POWERED_OFF) {
					if (os_acquire_i2c_mutex()) {
						status_sensor = mxc400x_set_power_mode(&mxc400x_data_global, false);
						os_release_i2c_mutex();
						if (status_sensor != NO_ERROR) {
							NRF_LOG_INFO("MXC400X sensor shutdown succesfully.");
							mxc400x_info.status = POWERED_OFF;
						}
						else {
							NRF_LOG_INFO("MXC400X sensor shutdown failed!");
							mxc400x_info.status = UNKNOWN;
						}
					}
				}
			}
			// Message not handled.
			else {
				NRF_LOG_INFO("Sensor D command ignored!");
			}
		}
		os_thread_yield();
	}
}

/**************************************************************
 * @brief  Function handles a repeated timer timeout event.
 * @param  None.
 **************************************************************/
void os_poll_timer_handler(void * p_context) {
	static MSGQUEUE_OBJ_t msg;
	static uint64_t time_offset_ms = 0;
	
	UNUSED_PARAMETER(p_context);
	
	// Update the real-time clock.
	set_system_time_ms(get_system_time_ms() + get_common_poll_period_ms());
	if ((get_system_time_ms() % 1000 == 0) || (get_system_time_ms() > 1000)) {
		set_system_time_ms(0);
		set_system_time_unix(get_system_time_unix() + 1);
		
		// Update the BLE timestamp characteristic, if required.
		#if ((USE_CUSTOM_SVC) && (!USE_CURRENT_TIME_SVC))
		timestamp_data_update(get_system_time_unix());
		#endif
	}
	
	// Update the time offset and poll counter.
	time_offset_ms += get_common_poll_period_ms();
	increment_num_polls();
	
	// Start sensor threads as and when required.
	msg.Idx = START;
	if ((shtc3_info.enabled) && (shtc3_info.mode)) {
		if ((time_offset_ms % shtc3_info.read_period) == 0) {
			os_message_queue_put(SENSOR_A_QUEUE, &msg, true);
		}
	}
	if ((ms5607_info.enabled) && (ms5607_info.mode)) {
		if ((time_offset_ms % ms5607_info.read_period) == 0) {
			os_message_queue_put(SENSOR_B_QUEUE, &msg, true);
		}
	}
	if ((tsl2x7x_info.enabled) && (tsl2x7x_info.mode)) {
		if ((time_offset_ms % tsl2x7x_info.read_period) == 0) {
			os_message_queue_put(SENSOR_C_QUEUE, &msg, true);
		}
	}
	if ((mxc400x_info.enabled) && (mxc400x_info.mode)) {
		if ((time_offset_ms % mxc400x_info.read_period) == 0) {
			os_message_queue_put(SENSOR_D_QUEUE, &msg, true);
		}
	}
}


/**************************************************************
 * @brief       Function for handling the battery measurement timer timeout.
 * @details     This function will be called each time the battery level measurement timer expires; this function will start the ADC.
 * @notes       None.
 * @param[in]   p_context   Pointer used for passing some arbitrary information (context) from the app_start_timer() call to the timeout handler.
 * @param[out]  None.
 **************************************************************/
void os_battery_timer_handler(void * p_context) {
	UNUSED_PARAMETER(p_context);
	measure_battery_level();
	set_system_status();
}


/**************************************************************
 * @brief  Function for initialising software timers.
 * @notes  None.
 * @param  None.
 **************************************************************/
void os_timers_init(void) {
#if !USE_FREERTOS
	ret_code_t err_code;
#endif
	
	NRF_LOG_INFO("Timers init!");
	
	// Initialise the timer module.
#if !USE_FREERTOS
    err_code = app_timer_init();
    APP_ERROR_CHECK(err_code);
#endif
	
	// Create a battery timer.
#if USE_FREERTOS
	#if FRERTOS_USE_STATIC_ALLOCATION
	m_battery_timer = xTimerCreateStatic("BATT", pdMS_TO_TICKS(BATTERY_MEAS_INT_MS), pdTRUE, 0, os_battery_timer_handler, &xBatteryTimerBuffer);
	if (m_battery_timer == NULL) {
		APP_ERROR_HANDLER(NRF_ERROR_NO_MEM);
	}
	#else
	m_battery_timer = xTimerCreate("BATT", pdMS_TO_TICKS(BATTERY_MEAS_INT_MS), pdTRUE, 0, os_battery_timer_handler);
	if (m_battery_timer == NULL) {
		APP_ERROR_HANDLER(NRF_ERROR_NO_MEM);
	}
	#endif
#else
	err_code = app_timer_create(&m_battery_timer_id, APP_TIMER_MODE_REPEATED, battery_timer_handler);
    APP_ERROR_CHECK(err_code);
#endif
	
	// Create a common poll timer.
#if USE_FREERTOS
	#if FRERTOS_USE_STATIC_ALLOCATION
	m_poll_timer = xTimerCreateStatic("POLL", pdMS_TO_TICKS(POLL_PERIOD_DEFAULT_MS), pdTRUE, 0, os_poll_timer_handler, &xPollTimerBuffer);
	if (m_poll_timer == NULL) {
		APP_ERROR_HANDLER(NRF_ERROR_NO_MEM);
	}
	#else
	m_poll_timer = xTimerCreate("POLL", pdMS_TO_TICKS(POLL_PERIOD_DEFAULT_MS), pdTRUE, 0, os_poll_timer_handler);
	if (m_poll_timer == NULL) {
		APP_ERROR_HANDLER(NRF_ERROR_NO_MEM);
	}
	#endif
#else
	err_code = app_timer_create(&m_poll_timer_id, APP_TIMER_MODE_REPEATED, poll_timer_handler);
	APP_ERROR_CHECK(err_code);
#endif
}


/**************************************************************
 * @brief  Function starts the battery read timer.
 * @notes  None.
 * @param  None.
 **************************************************************/
void os_battery_timer_start(uint32_t period_ms, bool fromISR) {
#if USE_FREERTOS
	if (fromISR) {
		BaseType_t xHigherPriorityTaskWoken = pdFALSE;
		if (xTimerChangePeriodFromISR(m_battery_timer, pdMS_TO_TICKS(period_ms), &xHigherPriorityTaskWoken) != pdPASS) {	// Also starts the timer.
			APP_ERROR_HANDLER(NRF_ERROR_NO_MEM);
		}
	}
	else {
		if (xTimerChangePeriod(m_battery_timer, pdMS_TO_TICKS(period_ms), portMAX_DELAY) != pdPASS) {	// Also starts the timer.
			APP_ERROR_HANDLER(NRF_ERROR_NO_MEM);
		}
	}
#else
    static ret_code_t err_code = app_timer_start(m_battery_timer_id, APP_TIMER_TICKS(period_ms), NULL);
	APP_ERROR_CHECK(err_code);
#endif
}


/**************************************************************
 * @brief  Function stops the battery timer.
 * @notes  None.
 * @param  None.
 **************************************************************/
void os_battery_timer_stop(bool fromISR) {
#if USE_FREERTOS
	if (m_battery_timer != NULL) {
		if (fromISR) {
			while(xTimerStop(m_battery_timer, portMAX_DELAY) != pdPASS);
		}
		else {
			BaseType_t xHigherPriorityTaskWoken = pdFALSE;
			while(xTimerStopFromISR(m_battery_timer, &xHigherPriorityTaskWoken) != pdPASS);
		}
	}
#else
    static ret_code_t err_code = app_timer_stop(m_battery_timer_id);
	APP_ERROR_CHECK(err_code);
#endif
}
	

/**************************************************************
 * @brief  Function starts the common poll timer.
 * @notes  None.
 * @param  None.
 **************************************************************/
void os_poll_timer_start(uint32_t period_ms, bool fromISR) {
#if USE_FREERTOS
	if (fromISR) {
		BaseType_t xHigherPriorityTaskWoken = pdFALSE;
		if (xTimerChangePeriodFromISR(m_poll_timer, pdMS_TO_TICKS(period_ms), &xHigherPriorityTaskWoken) != pdPASS) {	// Also starts the timer.
			APP_ERROR_HANDLER(NRF_ERROR_NO_MEM);
		}
	}
	else {
		if (xTimerChangePeriod(m_poll_timer, pdMS_TO_TICKS(period_ms), portMAX_DELAY) != pdPASS) {	// Also starts the timer.
			APP_ERROR_HANDLER(NRF_ERROR_NO_MEM);
		}
	}
#else
    static ret_code_t err_code = app_timer_start(m_poll_timer_id, APP_TIMER_TICKS(period_ms), NULL);
	APP_ERROR_CHECK(err_code);
#endif
}


/**************************************************************
 * @brief  Function stops the common poll timer.
 * @notes  None.
 * @param  None.
 **************************************************************/
void os_poll_timer_stop(bool fromISR) {
#if USE_FREERTOS
	if (m_poll_timer != NULL) {
		if (fromISR) {
			while(xTimerStop(m_poll_timer, portMAX_DELAY) != pdPASS);
		}
		else {
			BaseType_t xHigherPriorityTaskWoken = pdFALSE;
			while(xTimerStopFromISR(m_poll_timer, &xHigherPriorityTaskWoken) != pdPASS);
		}
	}
#else
    static ret_code_t err_code = app_timer_stop(m_poll_timer_id);
	APP_ERROR_CHECK(err_code);
#endif
}

Related