diff --git a/epicardium/epicardium.h b/epicardium/epicardium.h index b576662965726dcf83caefcb3287e87c791da319..0c21eee1dc3f092de7fb782eb23bd1ae4b70761f 100644 --- a/epicardium/epicardium.h +++ b/epicardium/epicardium.h @@ -111,6 +111,10 @@ typedef _Bool bool; #define API_BME680_DEINIT 0xD1 #define API_BME680_GET_DATA 0xD2 +#define API_BHI160_ENABLE 0xe0 +#define API_BHI160_DISABLE 0xe1 +#define API_BHI160_DISABLE_ALL 0xe2 + /* clang-format on */ typedef uint32_t api_int_id_t; @@ -151,9 +155,16 @@ API(API_INTERRUPT_DISABLE, int epic_interrupt_disable(api_int_id_t int_id)); #define EPIC_INT_UART_RX 2 /** RTC Alarm interrupt. See :c:func:`epic_isr_rtc_alarm` */ #define EPIC_INT_RTC_ALARM 3 +/** BHI */ +#define EPIC_INT_BHI160_ACCELEROMETER 4 +API_ISR(EPIC_INT_BHI160_ACCELEROMETER, epic_isr_bhi160_accelerometer); +#define EPIC_INT_BHI160_ORIENTATION 5 +API_ISR(EPIC_INT_BHI160_ORIENTATION, epic_isr_bhi160_orientation); +#define EPIC_INT_BHI160_GYROSCOPE 6 +API_ISR(EPIC_INT_BHI160_GYROSCOPE, epic_isr_bhi160_gyroscope); /* Number of defined interrupts. */ -#define EPIC_INT_NUM 4 +#define EPIC_INT_NUM 7 /* clang-format on */ /* @@ -859,6 +870,173 @@ API(API_PERSONAL_STATE_IS_PERSISTENT, int epic_personal_state_is_persistent()); */ API(API_STREAM_READ, int epic_stream_read(int sd, void *buf, size_t count)); +/** + * BHI160 Sensor Fusion + * ==================== + * card10 has a BHI160 onboard which is used as an IMU. BHI160 exposes a few + * different sensors which can be accessed using Epicardium API. + * + * **Example**: + * + * .. code-block:: cpp + * + * #include "epicardium.h" + * + * // Configure a sensor & enable it + * struct bhi160_sensor_config cfg = {0}; + * cfg.sample_buffer_len = 40; + * cfg.sample_rate = 4; // Hz + * cfg.dynamic_range = 2; // g + * + * int sd = epic_bhi160_enable_sensor(BHI160_ACCELEROMETER, &cfg); + * + * // Read sensor data + * while (1) { + * struct bhi160_data_vector buf[10]; + * + * int n = epic_stream_read(sd, buf, sizeof(buf)); + * + * for (int i = 0; i < n; i++) { + * printf("X: %6d Y: %6d Z: %6d\n", + * buf[i].x, + * buf[i].y, + * buf[i].z); + * } + * } + * + * // Disable the sensor + * epic_bhi160_disable_sensor(BHI160_ACCELEROMETER); + */ + +/** + * BHI160 Sensor Types + * ------------------- + */ + +/** + * BHI160 virtual sensor type. + */ +enum bhi160_sensor_type { + /** + * Accelerometer + * + * - Data type: :c:type:`bhi160_data_vector` + * - Dynamic range: g's (1x Earth Gravity, ~9.81m*s^-2) + */ + BHI160_ACCELEROMETER = 0, + /** Magnetometer (**Unimplemented**) */ + BHI160_MAGNETOMETER = 1, + /** Orientation */ + BHI160_ORIENTATION = 2, + /** Gyroscope */ + BHI160_GYROSCOPE = 3, + /** Gravity (**Unimplemented**) */ + BHI160_GRAVITY = 4, + /** Linear acceleration (**Unimplemented**) */ + BHI160_LINEAR_ACCELERATION = 5, + /** Rotation vector (**Unimplemented**) */ + BHI160_ROTATION_VECTOR = 6, + /** Uncalibrated magnetometer (**Unimplemented**) */ + BHI160_UNCALIBRATED_MAGNETOMETER = 7, + /** Game rotation vector (whatever that is supposed to be) */ + BHI160_GAME_ROTATION_VECTOR = 8, + /** Uncalibrated gyroscrope (**Unimplemented**) */ + BHI160_UNCALIBRATED_GYROSCOPE = 9, + /** Geomagnetic rotation vector (**Unimplemented**) */ + BHI160_GEOMAGNETIC_ROTATION_VECTOR = 10, +}; + +enum bhi160_data_type { + BHI160_DATA_TYPE_VECTOR +}; + +/** + * BHI160 Sensor Data Types + * ------------------------ + */ + +/** + * Vector Data. The scaling of these values is dependent on the chosen dynamic + * range. See the individual sensor's documentation for details. + */ +struct bhi160_data_vector { + enum bhi160_data_type data_type; + + /** X */ + int16_t x; + /** Y */ + int16_t y; + /** Z */ + int16_t z; + /** Status */ + uint8_t status; +}; + +/** + * BHI160 API + * ---------- + */ + +/** + * Configuration for a BHI160 sensor. + * + * This struct is used when enabling a sensor using + * :c:func:`epic_bhi160_enable_sensor`. + */ +struct bhi160_sensor_config { + /** + * Number of samples Epicardium should keep for this sensor. Do not set + * this number too high as the sample buffer will eat RAM. + */ + size_t sample_buffer_len; + /** + * Sample rate for the sensor in Hz. Maximum data rate is limited + * to 200 Hz for all sensors though some might be limited at a lower + * rate. + */ + uint16_t sample_rate; + /** + * Dynamic range. Interpretation of this value depends on + * the sensor type. Please refer to the specific sensor in + * :c:type:`bhi160_sensor_type` for details. + */ + uint16_t dynamic_range; + /** Always zero. Reserved for future parameters. */ + uint8_t _padding[8]; +}; + +/** + * Enable a BHI160 virtual sensor. Calling this funciton will instruct the + * BHI160 to collect data for this specific virtual sensor. You can then + * retrieve the samples using :c:func:`epic_stream_read`. + * + * :param bhi160_sensor_type sensor_type: Which sensor to enable. + * :param bhi160_sensor_config* config: Configuration for this sensor. + * :returns: A sensor descriptor which can be used with + * :c:func:`epic_stream_read` or a negative error value: + * + * - ``-EBUSY``: The BHI160 driver is currently busy with other tasks and + * could not be acquired for enabling a sensor. + */ +API(API_BHI160_ENABLE, int epic_bhi160_enable_sensor( + enum bhi160_sensor_type sensor_type, + struct bhi160_sensor_config *config +)); + +/** + * Disable a BHI160 sensor. + * + * :param bhi160_sensor_type sensor_type: Which sensor to disable. + */ +API(API_BHI160_DISABLE, int epic_bhi160_disable_sensor( + enum bhi160_sensor_type sensor_type +)); + +/** + * Disable all BHI160 sensors. + */ +API(API_BHI160_DISABLE_ALL, void epic_bhi160_disable_all_sensors()); + /** * Vibration Motor * =============== diff --git a/epicardium/main.c b/epicardium/main.c index 5e38e0bffe74c3dca93818bc40a50f5e1407fb09..8c260528f12a535501a22bbde882b8d4f5d0fa88 100644 --- a/epicardium/main.c +++ b/epicardium/main.c @@ -50,6 +50,18 @@ int main(void) abort(); } + /* BHI160 */ + if (xTaskCreate( + vBhi160Task, + (const char *)"BHI160 Driver", + configMINIMAL_STACK_SIZE * 2, + NULL, + tskIDLE_PRIORITY + 1, + NULL) != pdPASS) { + LOG_CRIT("startup", "Failed to create %s task!", "BHI160"); + abort(); + } + /* API */ if (xTaskCreate( vApiDispatcher, diff --git a/epicardium/meson.build b/epicardium/meson.build index 220f0bae63c7f84e60aee6002760c8271c7587de..ea28d3664563816c5fa8a4294e9fb3ba9308622c 100644 --- a/epicardium/meson.build +++ b/epicardium/meson.build @@ -80,7 +80,7 @@ elf = executable( l0der_sources, ble_sources, version_hdr, - dependencies: [libcard10, max32665_startup_core0, maxusb, libff13, ble], + dependencies: [libcard10, max32665_startup_core0, maxusb, libff13, ble, bhy1], link_with: [api_dispatcher_lib, freertos], link_whole: [max32665_startup_core0_lib, board_card10_lib, newlib_heap_lib], include_directories: [freertos_includes], diff --git a/epicardium/modules/bhi.c b/epicardium/modules/bhi.c new file mode 100644 index 0000000000000000000000000000000000000000..7d3689502e07c3de554744b1f10684d9d0af256b --- /dev/null +++ b/epicardium/modules/bhi.c @@ -0,0 +1,494 @@ +#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; + break; + } + + MXC_ASSERT(data_type == BHY_DATA_TYPE_VECTOR); + if (bhi160_streams[sensor_type].queue == NULL) { + break; + } + 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; + 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); + } + break; + 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); + + int lockret = hwlock_acquire(HWLOCK_I2C, pdMS_TO_TICKS(100)); + if (lockret < 0) { + 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); + } + + /* Wait for first interrupt */ + hwlock_release(HWLOCK_I2C); + ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(100)); + 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 */ + 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)); + } +} diff --git a/epicardium/modules/hardware.c b/epicardium/modules/hardware.c index 1173977a70f8a3a1acb4475ef6e42005eb282fee..3b2c0696e127e9681344404ca0dba19d95c5913c 100644 --- a/epicardium/modules/hardware.c +++ b/epicardium/modules/hardware.c @@ -259,6 +259,11 @@ int hardware_reset(void) */ display_init_slim(); + /* + * BHI160 + */ + epic_bhi160_disable_all_sensors(); + /* * BME680 Sensor */ diff --git a/epicardium/modules/meson.build b/epicardium/modules/meson.build index 73975adceee81bb83894ecdd15784112ce1f3221..736b3efec72ee9462833e574c8b1c7a5ae32ea01 100644 --- a/epicardium/modules/meson.build +++ b/epicardium/modules/meson.build @@ -1,4 +1,5 @@ module_sources = files( + 'bhi.c', 'bme680.c', 'buttons.c', 'dispatcher.c', diff --git a/epicardium/modules/modules.h b/epicardium/modules/modules.h index 8760e691567f4e89e989cfe48d43d4c0d05cb77b..d6d045f2e8cc4e45a59e7a9a10495fa0373f9333 100644 --- a/epicardium/modules/modules.h +++ b/epicardium/modules/modules.h @@ -84,4 +84,10 @@ int hwlock_release(enum hwlock_periph p); /* Forces an unlock of the display. Only to be used in Epicardium */ void disp_forcelock(); +/* ---------- BHI160 ------------------------------------------------------- */ +#define BHI160_FIFO_SIZE 128 +#define BHI160_MUTEX_WAIT_MS 50 +void vBhi160Task(void *pvParameters); + + #endif /* MODULES_H */ diff --git a/epicardium/modules/stream.h b/epicardium/modules/stream.h index 9d137a20e6fdf5b84e9e568b5e06a294069d1606..41064bd5d716bc340721bd7b36267d7a648ec45f 100644 --- a/epicardium/modules/stream.h +++ b/epicardium/modules/stream.h @@ -25,6 +25,10 @@ typedef unsigned int size_t; * Please keep IDs in sequential order. */ enum stream_descriptor { + /** BHI160 */ + SD_BHI160_ACCELEROMETER, + SD_BHI160_ORIENTATION, + SD_BHI160_GYROSCOPE, /** Highest descriptor must always be ``SD_MAX``. */ SD_MAX, }; diff --git a/preload/apps/bhi160/__init__.py b/preload/apps/bhi160/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..3632008c0375fe10d40c24096c56600be7f6c4dc --- /dev/null +++ b/preload/apps/bhi160/__init__.py @@ -0,0 +1,46 @@ +import bhi160 +import display +import utime +import buttons + +disp = display.open() +sensor = 0 + +sensors = [ + {"sensor": bhi160.BHI160Orientation(), "name": "Orientation"}, + {"sensor": bhi160.BHI160Accelerometer(), "name": "Accelerometer"}, + {"sensor": bhi160.BHI160Gyroscope(), "name": "Gyroscope"}, +] + +while True: + # Read and print sample + samples = sensors[sensor]["sensor"].read() + if len(samples) > 0: + disp.clear() + sample = samples[0] + + color = [255, 0, 0] + if sample.status == 1: + color = [255, 128, 0] + elif sample.status == 2: + color = [255, 255, 0] + elif sample.status == 3: + color = [0, 200, 0] + + disp.print(sensors[sensor]["name"], posy=0) + disp.print("X: %f" % sample.x, posy=20, fg=color) + disp.print("Y: %f" % sample.y, posy=40, fg=color) + disp.print("Z: %f" % sample.z, posy=60, fg=color) + + disp.update() + + # Read button + v = buttons.read(buttons.BOTTOM_RIGHT) + if v == 0: + button_pressed = False + + if not button_pressed and v & buttons.BOTTOM_RIGHT != 0: + button_pressed = True + sensor = (sensor + 1) % len(sensors) + + utime.sleep(0.1) diff --git a/preload/apps/bhi160/metadata.json b/preload/apps/bhi160/metadata.json new file mode 100644 index 0000000000000000000000000000000000000000..e8a0a37a4779bf3916449420f7724172b4eee021 --- /dev/null +++ b/preload/apps/bhi160/metadata.json @@ -0,0 +1 @@ +{"author": "card10badge team", "name": "BHI160", "description": "Read BHI160 sensor data", "category": "Hardware", "revision": 1} diff --git a/pycardium/meson.build b/pycardium/meson.build index f0a3798a6ab5ef5538673a98f6de9748e705f3b7..2c43f62f35d0dfed88fab3dfbd6bfcecdcd0a70d 100644 --- a/pycardium/meson.build +++ b/pycardium/meson.build @@ -1,6 +1,7 @@ name = 'pycardium' modsrc = files( + 'modules/bhi160-sys.c', 'modules/buttons.c', 'modules/fat_file.c', 'modules/fat_reader_import.c', diff --git a/pycardium/modules/bhi160-sys.c b/pycardium/modules/bhi160-sys.c new file mode 100644 index 0000000000000000000000000000000000000000..db0a5c9a65eb42f9b8ee392ffa5650909877bfb0 --- /dev/null +++ b/pycardium/modules/bhi160-sys.c @@ -0,0 +1,88 @@ +#include "py/obj.h" +#include "py/runtime.h" +#include "py/builtin.h" +#include "epicardium.h" +#include "api/common.h" +#include "mphalport.h" + +extern const mp_obj_type_t mp_type_bhi160_sample; + +STATIC mp_obj_t mp_bhi160_enable_sensor(size_t n_args, const mp_obj_t *args) +{ + int sensor_type = mp_obj_get_int(args[0]); + + struct bhi160_sensor_config cfg = { 0 }; + cfg.sample_buffer_len = mp_obj_get_int(args[1]); + cfg.sample_rate = mp_obj_get_int(args[2]); + cfg.dynamic_range = mp_obj_get_int(args[3]); + + int stream_id = epic_bhi160_enable_sensor(sensor_type, &cfg); + + return MP_OBJ_NEW_SMALL_INT(stream_id); +} + +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN( + mp_bhi160_enable_sensor_obj, 4, 4, mp_bhi160_enable_sensor +); + +STATIC mp_obj_t mp_bhi160_read_sensor(mp_obj_t stream_id_in) +{ + struct bhi160_data_vector buf[100]; + int stream_id = mp_obj_get_int(stream_id_in); + + int n = epic_stream_read(stream_id, buf, sizeof(buf)); + + mp_obj_list_t *list = mp_obj_new_list(0, NULL); + for (int i = 0; i < n; i++) { + if (buf[i].data_type != BHI160_DATA_TYPE_VECTOR) { + // other data types are currently not supported + mp_raise_OSError(EINVAL); + } + mp_obj_t tuple[4]; + tuple[0] = mp_obj_new_int(buf[i].x); + tuple[1] = mp_obj_new_int(buf[i].y); + tuple[2] = mp_obj_new_int(buf[i].z); + tuple[3] = mp_obj_new_int(buf[i].status); + mp_obj_list_append(list, mp_obj_new_tuple(4, tuple)); + } + + return MP_OBJ_FROM_PTR(list); +} + +STATIC MP_DEFINE_CONST_FUN_OBJ_1( + mp_bhi160_read_sensor_obj, mp_bhi160_read_sensor +); + +STATIC mp_obj_t mp_bhi160_disable_sensor(mp_obj_t sensor_type_in) +{ + int sensor_type = mp_obj_get_int(sensor_type_in); + + int ret = epic_bhi160_disable_sensor(sensor_type); + + return MP_OBJ_NEW_SMALL_INT(ret); +} + +STATIC MP_DEFINE_CONST_FUN_OBJ_1( + mp_bhi160_disable_sensor_obj, mp_bhi160_disable_sensor +); + +STATIC const mp_rom_map_elem_t bhi160_module_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_sys_bhi160) }, + { MP_ROM_QSTR(MP_QSTR_enable_sensor), + MP_ROM_PTR(&mp_bhi160_enable_sensor_obj) }, + { MP_ROM_QSTR(MP_QSTR_read_sensor), + MP_ROM_PTR(&mp_bhi160_read_sensor_obj) }, + { MP_ROM_QSTR(MP_QSTR_disable_sensor), + MP_ROM_PTR(&mp_bhi160_disable_sensor_obj) }, +}; +STATIC MP_DEFINE_CONST_DICT(bhi160_module_globals, bhi160_module_globals_table); + +// Define module object. +const mp_obj_module_t bhi160_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&bhi160_module_globals, +}; + +/* clang-format off */ +// Register the module to make it available in Python +MP_REGISTER_MODULE(MP_QSTR_sys_bhi160, bhi160_module, MODULE_BHI160_ENABLED); diff --git a/pycardium/modules/interrupt.c b/pycardium/modules/interrupt.c index 10770a19648a9bd9af182dfd58e2f0fddbf32def..927b936b906a8a7a8a952d12bf9940ab76ce8eff 100644 --- a/pycardium/modules/interrupt.c +++ b/pycardium/modules/interrupt.c @@ -85,6 +85,12 @@ static const mp_rom_map_elem_t interrupt_module_globals_table[] = { /* Interrupt Numbers */ { MP_ROM_QSTR(MP_QSTR_RTC_ALARM), MP_OBJ_NEW_SMALL_INT(EPIC_INT_RTC_ALARM) }, + { MP_ROM_QSTR(MP_QSTR_BHI160_ACCELEROMETER), + MP_OBJ_NEW_SMALL_INT(EPIC_INT_BHI160_ACCELEROMETER) }, + { MP_ROM_QSTR(MP_QSTR_BHI160_ORIENTATION), + MP_OBJ_NEW_SMALL_INT(EPIC_INT_BHI160_ORIENTATION) }, + { MP_ROM_QSTR(MP_QSTR_BHI160_GYROSCOPE), + MP_OBJ_NEW_SMALL_INT(EPIC_INT_BHI160_GYROSCOPE) }, }; static MP_DEFINE_CONST_DICT( interrupt_module_globals, interrupt_module_globals_table diff --git a/pycardium/modules/py/bhi160.py b/pycardium/modules/py/bhi160.py new file mode 100644 index 0000000000000000000000000000000000000000..86ad6f01f10b4f929d3390d41dda8283badb3676 --- /dev/null +++ b/pycardium/modules/py/bhi160.py @@ -0,0 +1,121 @@ +import sys_bhi160 +import interrupt +import ucollections + +DataVector = ucollections.namedtuple("DataVector", ["x", "y", "z", "status"]) + + +class BHI160: + def enable_sensor(self): + interrupt.disable_callback(self.interrupt_id) + interrupt.set_callback(self.interrupt_id, self._interrupt) + self.stream_id = sys_bhi160.enable_sensor( + self.sensor_id, self.sample_buffer_len, self.sample_rate, self.dynamic_range + ) + + if self.stream_id < 0: + raise ValueError("Enable sensor returned %i", self.stream_id) + + self.active = True + + if self._callback: + interrupt.enable_callback(self.interrupt_id) + + def __enter__(self): + return self + + def __exit__(self, _et, _ev, _t): + self.close() + + def close(self): + if self.active: + self.active = False + ret = sys_bhi160.disable_sensor(self.sensor_id) + + if ret < 0: + raise ValueError("Disable sensor returned %i", ret) + + interrupt.disable_callback(self.interrupt_id) + interrupt.set_callback(self.interrupt_id, None) + + def read(self): + result = [] + if self.active: + for sample in sys_bhi160.read_sensor(self.stream_id): + result.append(self.convert(sample)) + return result + + def _interrupt(self, _): + if self.active: + data = self.read() + print(data) + if self._callback: + self._callback(data) + + def convert_data_vector(self, sample): + return DataVector( + self.convert_single(sample[0]), + self.convert_single(sample[1]), + self.convert_single(sample[2]), + sample[3], + ) + + +class BHI160Accelerometer(BHI160): + def __init__( + self, sample_rate=4, dynamic_range=2, callback=None, sample_buffer_len=200 + ): + self.sample_rate = sample_rate + self.dynamic_range = dynamic_range + self.callback = callback + self.sample_buffer_len = sample_buffer_len + self.sensor_id = 0 + self.interrupt_id = interrupt.BHI160_ACCELEROMETER + self._callback = callback + self.enable_sensor() + + def convert_single(self, value): + return 2 * value / 32768.0 + + def convert(self, sample): + return self.convert_data_vector(sample) + + +class BHI160Gyroscope(BHI160): + def __init__( + self, sample_rate=4, dynamic_range=2, callback=None, sample_buffer_len=200 + ): + self.sample_rate = sample_rate + self.dynamic_range = dynamic_range + self.callback = callback + self.sample_buffer_len = sample_buffer_len + self.sensor_id = 3 + self.interrupt_id = interrupt.BHI160_GYROSCOPE + self._callback = callback + self.enable_sensor() + + def convert_single(self, value): + return 360 * value / 32768.0 + + def convert(self, sample): + return self.convert_data_vector(sample) + + +class BHI160Orientation(BHI160): + def __init__( + self, sample_rate=4, dynamic_range=2, callback=None, sample_buffer_len=200 + ): + self.sample_rate = sample_rate + self.dynamic_range = dynamic_range + self.callback = callback + self.sample_buffer_len = sample_buffer_len + self.sensor_id = 2 + self.interrupt_id = interrupt.BHI160_ORIENTATION + self._callback = callback + self.enable_sensor() + + def convert_single(self, value): + return 360 * value / 32768.0 + + def convert(self, sample): + return self.convert_data_vector(sample) diff --git a/pycardium/modules/py/meson.build b/pycardium/modules/py/meson.build index 59dd24d1fa95143f80c3f728129d6a3c5c170a0d..5548fff7e9fb15c17fec2f6c1c8d9add23a6541d 100644 --- a/pycardium/modules/py/meson.build +++ b/pycardium/modules/py/meson.build @@ -1,4 +1,5 @@ python_modules = files( + 'bhi160.py', 'color.py', 'htmlcolor.py', 'display.py', diff --git a/pycardium/modules/qstrdefs.h b/pycardium/modules/qstrdefs.h index 60c5c56c9cbc385bb87fb8fe33f2f4881fd41947..82c1d12577ce06917fa8a94408f2961b2fbb5905 100644 --- a/pycardium/modules/qstrdefs.h +++ b/pycardium/modules/qstrdefs.h @@ -57,9 +57,20 @@ Q(vibrate) Q(set_callback) Q(enable_callback) Q(disable_callback) -Q(BHI160) +Q(BHI160_ACCELEROMETER) +Q(BHI160_ORIENTATION) +Q(BHI160_GYROSCOPE) Q(RTC_ALARM) +/* bhi160 */ +Q(sys_bhi160) +Q(enable_sensor) +Q(disable_sensor) +Q(read_sensor) +Q(x) +Q(y) +Q(z) + /* display */ Q(sys_display) Q(display) diff --git a/pycardium/mpconfigport.h b/pycardium/mpconfigport.h index af27e146f99e46735e5f33bd013383dd47233fa7..b84867980a9858cdd6f9ec56398645ab4a9af8cd 100644 --- a/pycardium/mpconfigport.h +++ b/pycardium/mpconfigport.h @@ -45,6 +45,7 @@ int mp_hal_trng_read_int(void); #define MICROPY_PY_UERRNO (1) /* Modules */ +#define MODULE_BHI160_ENABLED (1) #define MODULE_BME680_ENABLED (1) #define MODULE_BUTTONS_ENABLED (1) #define MODULE_DISPLAY_ENABLED (1)