Skip to content
Snippets Groups Projects
bhi.c 12.4 KiB
Newer Older
  • Learn to ignore specific revisions
  • #include <stdio.h>
    #include <string.h>
    
    #include "gpio.h"
    #include "bhy_uc_driver.h"
    #include "bhy.h"
    #include "pmic.h"
    
    #include "FreeRTOS.h"
    #include "task.h"
    #include "semphr.h"
    #include "queue.h"
    
    
    #include "api/interrupt-sender.h"
    
    #include "epicardium.h"
    #include "modules/log.h"
    #include "modules/modules.h"
    #include "modules/stream.h"
    
    /* Ticks to wait when trying to acquire lock */
    #define LOCK_WAIT pdMS_TO_TICKS(BHI160_MUTEX_WAIT_MS)
    
    /* BHI160 Firmware Blob.  Contents are defined in libcard10. */
    extern uint8_t bhy1_fw[];
    
    /* Interrupt Pin */
    static const gpio_cfg_t bhi160_interrupt_pin = {
    	PORT_0, PIN_13, GPIO_FUNC_IN, GPIO_PAD_PULL_UP
    };
    
    /* Axis remapping matrices */
    static int8_t bhi160_mapping_matrix[3 * 3] = { 0, -1, 0, 1, 0, 0, 0, 0, 1 };
    static int8_t bmm150_mapping_matrix[3 * 3] = { -1, 0, 0, 0, 1, 0, 0, 0, -1 };
    
    /*
     * From the official docs:
     *
     *    The sic matrix should be calculated for customer platform by logging
     *    uncalibrated magnetometer data.  The sic matrix here is only an example
     *    array (identity matrix). Customer should generate their own matrix.  This
     *    affects magnetometer fusion performance.
     *
     * TODO: Get data for card10
     */
    /* clang-format off */
    static float bhi160_sic_array[3 * 3] = { 1.0, 0.0, 0.0,
                                             0.0, 1.0, 0.0,
                                             0.0, 0.0, 1.0 };
    /* clang-format on */
    
    /* BHI160 Fifo */
    static uint8_t bhi160_fifo[BHI160_FIFO_SIZE];
    static size_t start_index = 0;
    
    /* BHI160 Task ID */
    static TaskHandle_t bhi160_task_id = NULL;
    
    /* BHI160 Mutex */
    static StaticSemaphore_t bhi160_mutex_data;
    static SemaphoreHandle_t bhi160_mutex = NULL;
    
    /* Streams */
    static struct stream_info bhi160_streams[10];
    
    
    /* Active */
    static bool bhi160_sensor_active[10] = { 0 };
    
    
    /* -- Utilities -------------------------------------------------------- {{{ */
    /*
     * Retrieve the data size for a sensor.  This value is needed for the creation
     * of the sensor's sample queue.
     */
    static size_t bhi160_lookup_data_size(enum bhi160_sensor_type type)
    {
    	switch (type) {
    	case BHI160_ACCELEROMETER:
    	case BHI160_MAGNETOMETER:
    	case BHI160_ORIENTATION:
    
    	case BHI160_GYROSCOPE:
    
    		return sizeof(struct bhi160_data_vector);
    	default:
    		return 0;
    	}
    }
    
    /*
     * Map a sensor type to the virtual sensor ID used by BHy1.
     */
    static bhy_virtual_sensor_t bhi160_lookup_vs_id(enum bhi160_sensor_type type)
    {
    	switch (type) {
    	case BHI160_ACCELEROMETER:
    		return VS_ID_ACCELEROMETER;
    
    	case BHI160_ORIENTATION:
    		return VS_ID_ORIENTATION;
    
    	case BHI160_GYROSCOPE:
    		return VS_ID_GYROSCOPE;
    
    	default:
    		return -1;
    	}
    }
    
    /*
     * Map a sensor type to its stream descriptor.
     */
    static int bhi160_lookup_sd(enum bhi160_sensor_type type)
    {
    	switch (type) {
    	case BHI160_ACCELEROMETER:
    		return SD_BHI160_ACCELEROMETER;
    
    	case BHI160_ORIENTATION:
    		return SD_BHI160_ORIENTATION;
    
    	case BHI160_GYROSCOPE:
    		return SD_BHI160_GYROSCOPE;
    
    	default:
    		return -1;
    	}
    }
    /* }}} */
    
    /* -- API -------------------------------------------------------------- {{{ */
    int epic_bhi160_enable_sensor(
    	enum bhi160_sensor_type sensor_type,
    	struct bhi160_sensor_config *config
    ) {
    
    	int result = 0;
    
    
    	bhy_virtual_sensor_t vs_id = bhi160_lookup_vs_id(sensor_type);
    	if (vs_id < 0) {
    		return -ENODEV;
    	}
    
    
    	result = hwlock_acquire(HWLOCK_I2C, pdMS_TO_TICKS(100));
    	if (result < 0) {
    		return result;
    
    	if (xSemaphoreTake(bhi160_mutex, LOCK_WAIT) != pdTRUE) {
    		result = -EBUSY;
    		goto out_free_i2c;
    	}
    
    	struct stream_info *stream = &bhi160_streams[sensor_type];
    	stream->item_size          = bhi160_lookup_data_size(sensor_type);
    	/* TODO: Sanity check length */
    	stream->queue =
    		xQueueCreate(config->sample_buffer_len, stream->item_size);
    	if (stream->queue == NULL) {
    		result = -ENOMEM;
    		goto out_free_both;
    	}
    
    	result = stream_register(bhi160_lookup_sd(sensor_type), stream);
    	if (result < 0) {
    		goto out_free_both;
    	}
    
    	result = bhy_enable_virtual_sensor(
    		vs_id,
    		VS_WAKEUP,
    		config->sample_rate,
    		0,
    		VS_FLUSH_NONE,
    		0,
    		config->dynamic_range /* dynamic range is sensor dependent */
    	);
    	if (result != BHY_SUCCESS) {
    		goto out_free_both;
    
    	bhi160_sensor_active[sensor_type] = true;
    	result                            = bhi160_lookup_sd(sensor_type);
    
    out_free_both:
    	xSemaphoreGive(bhi160_mutex);
    out_free_i2c:
    
    	hwlock_release(HWLOCK_I2C);
    
    	return result;
    
    }
    
    int epic_bhi160_disable_sensor(enum bhi160_sensor_type sensor_type)
    {
    
    	int result = 0;
    
    
    	bhy_virtual_sensor_t vs_id = bhi160_lookup_vs_id(sensor_type);
    	if (vs_id < 0) {
    		return -ENODEV;
    	}
    
    
    	result = hwlock_acquire(HWLOCK_I2C, pdMS_TO_TICKS(100));
    	if (result < 0) {
    		return result;
    
    	if (xSemaphoreTake(bhi160_mutex, LOCK_WAIT) != pdTRUE) {
    		result = -EBUSY;
    		goto out_free_i2c;
    	}
    
    	struct stream_info *stream = &bhi160_streams[sensor_type];
    	result = stream_deregister(bhi160_lookup_sd(sensor_type), stream);
    	if (result < 0) {
    		goto out_free_both;
    	}
    
    	vQueueDelete(stream->queue);
    	stream->queue = NULL;
    	result        = bhy_disable_virtual_sensor(vs_id, VS_WAKEUP);
    	if (result < 0) {
    		goto out_free_both;
    
    	bhi160_sensor_active[sensor_type] = false;
    
    	result = 0;
    out_free_both:
    	xSemaphoreGive(bhi160_mutex);
    out_free_i2c:
    
    	hwlock_release(HWLOCK_I2C);
    
    	return result;
    
    
    void epic_bhi160_disable_all_sensors()
    {
    	for (int i = 0; i < sizeof(bhi160_sensor_active); i++) {
    		if (bhi160_sensor_active[i]) {
    			epic_bhi160_disable_sensor(i);
    		}
    	}
    }
    
    
    /* }}} */
    
    /* -- Driver ----------------------------------------------------------- {{{ */
    /*
     * Handle a single packet from the FIFO.  For most sensors this means pushing
     * the sample into its sample queue.
     */
    static void
    bhi160_handle_packet(bhy_data_type_t data_type, bhy_data_generic_t *sensor_data)
    {
    	uint8_t sensor_id = sensor_data->data_vector.sensor_id;
    	struct bhi160_data_vector data_vector;
    	/*
    	 * Timestamp of the next samples, counting at 32 kHz.
    	 * Currently unused.
    	 */
    
    	static uint32_t timestamp           = 0;
    	enum bhi160_sensor_type sensor_type = 0;
    	int epic_int                        = 0;
    	bool wakeup                         = false;
    
    
    	switch (sensor_id) {
    	case VS_ID_TIMESTAMP_MSW_WAKEUP:
    
    		wakeup = true;
    		/* fall through */
    	case VS_ID_TIMESTAMP_MSW:
    
    		MXC_ASSERT(data_type == BHY_DATA_TYPE_SCALAR_U16);
    		timestamp = sensor_data->data_scalar_u16.data << 16;
    		break;
    	case VS_ID_TIMESTAMP_LSW_WAKEUP:
    
    		wakeup = true;
    		/* fall through */
    	case VS_ID_TIMESTAMP_LSW:
    
    		MXC_ASSERT(data_type == BHY_DATA_TYPE_SCALAR_U16);
    		timestamp = (timestamp & 0xFFFF0000) |
    			    sensor_data->data_scalar_u16.data;
    		break;
    	case VS_ID_ACCELEROMETER_WAKEUP:
    
    	case VS_ID_ORIENTATION_WAKEUP:
    
    	case VS_ID_GYROSCOPE_WAKEUP:
    		wakeup = true;
    		/* fall through */
    	case VS_ID_ACCELEROMETER:
    
    	case VS_ID_ORIENTATION:
    
    	case VS_ID_GYROSCOPE:
    		switch (sensor_id) {
    		case VS_ID_ACCELEROMETER_WAKEUP:
    		case VS_ID_ACCELEROMETER:
    			sensor_type = BHI160_ACCELEROMETER;
    			epic_int    = EPIC_INT_BHI160_ACCELEROMETER;
    			break;
    
    		case VS_ID_ORIENTATION_WAKEUP:
    		case VS_ID_ORIENTATION:
    			sensor_type = BHI160_ORIENTATION;
    			epic_int    = EPIC_INT_BHI160_ORIENTATION;
    			break;
    
    		case VS_ID_GYROSCOPE_WAKEUP:
    		case VS_ID_GYROSCOPE:
    			sensor_type = BHI160_GYROSCOPE;
    			epic_int    = EPIC_INT_BHI160_GYROSCOPE;
    
    		MXC_ASSERT(data_type == BHY_DATA_TYPE_VECTOR);
    
    		if (bhi160_streams[sensor_type].queue == NULL) {
    
    		data_vector.data_type = BHI160_DATA_TYPE_VECTOR;
    		data_vector.x         = sensor_data->data_vector.x;
    		data_vector.y         = sensor_data->data_vector.y;
    		data_vector.z         = sensor_data->data_vector.z;
    
    koalo's avatar
    koalo committed
    		data_vector.status    = sensor_data->data_vector.status;
    
    		xQueueSend(
    
    			bhi160_streams[sensor_type].queue,
    
    			&data_vector,
    			BHI160_MUTEX_WAIT_MS
    		);
    
    		if (wakeup) {
    			api_interrupt_trigger(epic_int);
    
    	default:
    		break;
    	}
    }
    
    /*
     * Fetch all data available from BHI160's FIFO buffer and handle all packets
     * contained in it.
     */
    static int bhi160_fetch_fifo(void)
    {
    	/*
    	 * Warning:  The code from the BHy1 docs has some issues.  This
    	 * implementation looks similar, but has a few important differences.
    	 * You'll probably be best of leaving it as it is ...
    	 */
    
    
    	int result = 0;
    
    	/* Number of bytes left in BHI160's FIFO buffer */
    	uint16_t bytes_left_in_fifo = 1;
    
    
    	result = hwlock_acquire(HWLOCK_I2C, pdMS_TO_TICKS(100));
    	if (result < 0) {
    		return result;
    
    	if (xSemaphoreTake(bhi160_mutex, LOCK_WAIT) != pdTRUE) {
    
    		result = -EBUSY;
    		goto out_free_i2c;
    
    	}
    
    	while (bytes_left_in_fifo) {
    		/* Fill local FIFO buffer with as many bytes as possible */
    		uint16_t bytes_read;
    		bhy_read_fifo(
    			&bhi160_fifo[start_index],
    			BHI160_FIFO_SIZE - start_index,
    			&bytes_read,
    			&bytes_left_in_fifo
    		);
    
    		/* Add the bytes left from the last transfer on top */
    		bytes_read += start_index;
    
    		/* Handle all full packets received in this transfer */
    		uint8_t *fifo_ptr   = bhi160_fifo;
    		uint16_t bytes_left = bytes_read;
    
    		while (bytes_left > 0) {
    
    			bhy_data_generic_t sensor_data;
    			bhy_data_type_t data_type;
    
    			result = bhy_parse_next_fifo_packet(
    
    				&fifo_ptr,
    				&bytes_left,
    				&sensor_data,
    				&data_type
    			);
    
    
    			if (result == BHY_SUCCESS) {
    
    				bhi160_handle_packet(data_type, &sensor_data);
    
    			} else {
    				break;
    
    			}
    		}
    
    		/* Shift the remaining bytes to the beginning */
    		for (int i = 0; i < bytes_left; i++) {
    			bhi160_fifo[i] =
    				bhi160_fifo[bytes_read - bytes_left + i];
    		}
    		start_index = bytes_left;
    	}
    
    	xSemaphoreGive(bhi160_mutex);
    
    out_free_i2c:
    
    	hwlock_release(HWLOCK_I2C);
    
    	return result;
    
    }
    
    /*
     * Callback for the BHI160 interrupt pin.  This callback is called from the
     * SDK's GPIO interrupt driver, in interrupt context.
     */
    static void bhi160_interrupt_callback(void *_)
    {
    	BaseType_t xHigherPriorityTaskWoken = pdFALSE;
    
    	if (bhi160_task_id != NULL) {
    		vTaskNotifyGiveFromISR(
    			bhi160_task_id, &xHigherPriorityTaskWoken
    		);
    		portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
    	}
    }
    /* }}} */
    
    void vBhi160Task(void *pvParameters)
    {
    	int ret;
    
    	bhi160_task_id = xTaskGetCurrentTaskHandle();
    	bhi160_mutex   = xSemaphoreCreateMutexStatic(&bhi160_mutex_data);
    
    
    	/*
    	 * Wait a little before initializing BHI160.
    	 */
    
    	vTaskDelay(pdMS_TO_TICKS(3));
    
    	int lockret = hwlock_acquire(HWLOCK_I2C, pdMS_TO_TICKS(100));
    	if (lockret < 0) {
    
    koalo's avatar
    koalo committed
    		LOG_CRIT("bhi160", "Failed to acquire I2C lock!");
    		vTaskDelay(portMAX_DELAY);
    
    	/* Take Mutex during initialization, just in case */
    	if (xSemaphoreTake(bhi160_mutex, 0) != pdTRUE) {
    		LOG_CRIT("bhi160", "Failed to acquire BHI160 mutex!");
    		vTaskDelay(portMAX_DELAY);
    	}
    
    	memset(bhi160_streams, 0x00, sizeof(bhi160_streams));
    
    	/* Install interrupt callback */
    	GPIO_Config(&bhi160_interrupt_pin);
    	GPIO_RegisterCallback(
    		&bhi160_interrupt_pin, bhi160_interrupt_callback, NULL
    	);
    	GPIO_IntConfig(&bhi160_interrupt_pin, GPIO_INT_EDGE, GPIO_INT_RISING);
    	GPIO_IntEnable(&bhi160_interrupt_pin);
    	NVIC_SetPriority(
    		(IRQn_Type)MXC_GPIO_GET_IRQ(bhi160_interrupt_pin.port), 2
    	);
    	NVIC_EnableIRQ((IRQn_Type)MXC_GPIO_GET_IRQ(bhi160_interrupt_pin.port));
    
    	/* Upload firmware */
    	ret = bhy_driver_init(bhy1_fw);
    	if (ret) {
    		LOG_CRIT("bhi160", "BHy1 init failed!");
    		vTaskDelay(portMAX_DELAY);
    	}
    
    
    koalo's avatar
    koalo committed
    	/* Wait for first interrupt */
    	hwlock_release(HWLOCK_I2C);
    
    	ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(100));
    
    koalo's avatar
    koalo committed
    	lockret = hwlock_acquire(HWLOCK_I2C, pdMS_TO_TICKS(100));
    	if (lockret < 0) {
    		LOG_CRIT("bhi160", "Failed to acquire I2C lock!");
    		vTaskDelay(portMAX_DELAY);
    	}
    
    
    	/* Remap axes to match card10 layout */
    
    	/* Due to a known issue (#133) the first call to
    	 * bhy_mapping_matrix_set might fail. */
    	bhy_mapping_matrix_set(
    		PHYSICAL_SENSOR_INDEX_ACC, bhi160_mapping_matrix
    	);
    
    	bhy_mapping_matrix_set(
    		PHYSICAL_SENSOR_INDEX_ACC, bhi160_mapping_matrix
    	);
    	bhy_mapping_matrix_set(
    		PHYSICAL_SENSOR_INDEX_MAG, bmm150_mapping_matrix
    	);
    	bhy_mapping_matrix_set(
    		PHYSICAL_SENSOR_INDEX_GYRO, bhi160_mapping_matrix
    	);
    
    	/* Set "SIC" matrix.  TODO: Find out what this is about */
    	bhy_set_sic_matrix(bhi160_sic_array);
    
    	xSemaphoreGive(bhi160_mutex);
    
    	hwlock_release(HWLOCK_I2C);
    
    
    	/* ----------------------------------------- */
    
    	while (1) {
    		int ret = bhi160_fetch_fifo();
    		if (ret == -EBUSY) {
    			LOG_WARN("bhi160", "Could not acquire mutex for FIFO?");
    			continue;
    		} else if (ret < 0) {
    			LOG_ERR("bhi160", "Unknown error: %d", -ret);
    		}
    
    		/*
    		 * Wait for interrupt.  After two seconds, fetch FIFO anyway in
    		 * case there are any diagnostics or errors.
    		 *
    		 * In the future, reads using epic_stream_read() might also
    		 * trigger a FIFO fetch, from outside this task.
    		 */
    		ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(2000));
    	}
    }