From 91b2c78a43d143fc95b32449b02f00594fdcc5c8 Mon Sep 17 00:00:00 2001
From: Rahix <rahix@rahix.de>
Date: Sat, 6 Jul 2019 23:26:06 +0200
Subject: [PATCH] feat(bma400): Add basic API and pycardium module

Signed-off-by: Rahix <rahix@rahix.de>
---
 Documentation/index.rst            |   1 +
 Documentation/pycardium/bma400.rst |   8 +++
 epicardium/epicardium.h            |  33 +++++++++
 epicardium/modules/bma400.c        | 104 +++++++++++++++++++++++++++++
 epicardium/modules/meson.build     |   1 +
 pycardium/meson.build              |   1 +
 pycardium/modules/bma400.c         |  37 ++++++++++
 pycardium/modules/qstrdefs.h       |   4 ++
 pycardium/mpconfigport.h           |   1 +
 9 files changed, 190 insertions(+)
 create mode 100644 Documentation/pycardium/bma400.rst
 create mode 100644 epicardium/modules/bma400.c
 create mode 100644 pycardium/modules/bma400.c

diff --git a/Documentation/index.rst b/Documentation/index.rst
index 1fe90f2e..f4b3defd 100644
--- a/Documentation/index.rst
+++ b/Documentation/index.rst
@@ -21,6 +21,7 @@ Last but not least, if you want to start hacking the lower-level firmware, the
    :caption: Pycardium
 
    pycardium/overview
+   pycardium/bma400
    pycardium/color
    pycardium/display
    pycardium/leds
diff --git a/Documentation/pycardium/bma400.rst b/Documentation/pycardium/bma400.rst
new file mode 100644
index 00000000..77e5390a
--- /dev/null
+++ b/Documentation/pycardium/bma400.rst
@@ -0,0 +1,8 @@
+``bma400`` - Accelerometer
+==========================
+
+.. py:function:: bma400.get_accel()
+
+   Get acceleration vector.
+
+   :return: Tuple containing ``x``, ``y``, and ``z`` acceleration.
diff --git a/epicardium/epicardium.h b/epicardium/epicardium.h
index cb3d9b42..5828c06b 100644
--- a/epicardium/epicardium.h
+++ b/epicardium/epicardium.h
@@ -67,6 +67,8 @@ typedef unsigned int size_t;
 #define API_LIGHT_SENSOR_RUN       0x80
 #define API_LIGHT_SENSOR_GET       0x81
 #define API_LIGHT_SENSOR_STOP      0x82
+
+#define API_BMA400_GET_ACCEL       0x90
 /* clang-format on */
 
 typedef uint32_t api_int_id_t;
@@ -279,6 +281,37 @@ API(API_LEDS_SET, void epic_leds_set(int led, uint8_t r, uint8_t g, uint8_t b));
  */
 API(API_STREAM_READ, int epic_stream_read(int sd, void *buf, size_t count));
 
+/**
+ * BMA400
+ * ======
+ */
+
+/**
+ * An acceleration vector.
+ */
+struct acceleration {
+	/** Acceleration component along x axis. */
+	float x;
+	/** Acceleration component along y axis. */
+	float y;
+	/** Acceleration component along z axis. */
+	float z;
+};
+
+/**
+ * Get the current acceleration vector.
+ *
+ * :param data: Where to store the acceleration vector.
+ * :return: 0 on success or ``-Exxx`` on error.  The following
+ *     errors might occur:
+ *
+ *     - ``-EFAULT``:  On NULL-pointer.
+ *     - ``-EINVAL``:  Invalid configuration.
+ *     - ``-EIO``:  Communication with the device failed.
+ *     - ``-ENODEV``:  Device was not found.
+ */
+API(API_BMA400_GET_ACCEL, int epic_bma400_get_accel(struct acceleration *data));
+
 /**
  * Vibration Motor
  * ===============
diff --git a/epicardium/modules/bma400.c b/epicardium/modules/bma400.c
new file mode 100644
index 00000000..8520aa68
--- /dev/null
+++ b/epicardium/modules/bma400.c
@@ -0,0 +1,104 @@
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+
+#include "bma400.h"
+#include "bosch.h"
+#include "card10.h"
+
+#include "epicardium.h"
+#include "modules/log.h"
+
+static bool initialized;
+static struct bma400_dev bma400;
+
+#define GRAVITY_EARTH (9.80665f) /* Earth's gravity in m/s^2 */
+static float lsb_to_ms2(int16_t val, float g_range, uint8_t bit_width)
+{
+	float half_scale = (float)(1 << bit_width) / 2.0f;
+	return GRAVITY_EARTH * val * g_range / half_scale;
+}
+
+static int convert_error(int8_t error)
+{
+	switch (error) {
+	case BMA400_E_NULL_PTR:
+		return EFAULT;
+	case BMA400_E_COM_FAIL:
+		return EIO;
+	case BMA400_E_DEV_NOT_FOUND:
+		return ENODEV;
+	case BMA400_E_INVALID_CONFIG:
+		return EINVAL;
+	default:
+		return 1;
+	}
+}
+
+int epic_bma400_get_accel(struct acceleration *data)
+{
+	uint8_t result;
+
+	if (__builtin_expect(!initialized, 0)) {
+		bma400.intf_ptr = NULL;
+		bma400.delay_ms = card10_bosch_delay;
+		bma400.dev_id   = BMA400_I2C_ADDRESS_SDO_LOW;
+		bma400.read     = card10_bosch_i2c_read_ex;
+		bma400.write    = card10_bosch_i2c_write_ex;
+		bma400.intf     = BMA400_I2C_INTF;
+
+		result = bma400_init(&bma400);
+		if (result != BMA400_OK) {
+			LOG_ERR("bma400", "init error: %d", result);
+			return -convert_error(result);
+		}
+
+		result = bma400_soft_reset(&bma400);
+		if (result != BMA400_OK) {
+			LOG_ERR("bma400", "soft_reset error: %d", result);
+			return -convert_error(result);
+		}
+
+		struct bma400_sensor_conf conf;
+		conf.type = BMA400_ACCEL;
+
+		result = bma400_get_sensor_conf(&conf, 1, &bma400);
+		if (result != BMA400_OK) {
+			LOG_ERR("bma400", "get_sensor_conf error: %d", result);
+			return -convert_error(result);
+		}
+
+		conf.param.accel.odr      = BMA400_ODR_100HZ;
+		conf.param.accel.range    = BMA400_2G_RANGE;
+		conf.param.accel.data_src = BMA400_DATA_SRC_ACCEL_FILT_1;
+
+		result = bma400_set_sensor_conf(&conf, 1, &bma400);
+		if (result != BMA400_OK) {
+			LOG_ERR("bma400", "set_sensor_conf error: %d", result);
+			return -convert_error(result);
+		}
+
+		result = bma400_set_power_mode(BMA400_LOW_POWER_MODE, &bma400);
+		if (result != BMA400_OK) {
+			LOG_ERR("bma400", "set_power_mode error: %d", result);
+			return -convert_error(result);
+		}
+
+		initialized = true;
+	}
+
+	struct bma400_sensor_data data_in;
+	result = bma400_get_accel_data(
+		BMA400_DATA_SENSOR_TIME, &data_in, &bma400
+	);
+	if (result != BMA400_OK) {
+		LOG_ERR("bma400", "get_accel_data error: %d\n", result);
+		return -convert_error(result);
+	}
+
+	data->x = lsb_to_ms2(data_in.x, 2, 12);
+	data->y = lsb_to_ms2(data_in.y, 2, 12);
+	data->z = lsb_to_ms2(data_in.z, 2, 12);
+
+	return 0;
+}
diff --git a/epicardium/modules/meson.build b/epicardium/modules/meson.build
index 113e3cb8..d67e6616 100644
--- a/epicardium/modules/meson.build
+++ b/epicardium/modules/meson.build
@@ -1,4 +1,5 @@
 module_sources = files(
+  'bma400.c',
   'display.c',
   'fatfs.c',
   'fatfs_fileops.c',
diff --git a/pycardium/meson.build b/pycardium/meson.build
index cf5f5ba5..6e1f9260 100644
--- a/pycardium/meson.build
+++ b/pycardium/meson.build
@@ -1,6 +1,7 @@
 name = 'pycardium'
 
 modsrc = files(
+  'modules/bma400.c',
   'modules/interrupt.c',
   'modules/leds.c',
   'modules/sys_display.c',
diff --git a/pycardium/modules/bma400.c b/pycardium/modules/bma400.c
new file mode 100644
index 00000000..e09e1695
--- /dev/null
+++ b/pycardium/modules/bma400.c
@@ -0,0 +1,37 @@
+#include "py/obj.h"
+#include "py/objlist.h"
+#include "py/runtime.h"
+
+#include "epicardium.h"
+
+static mp_obj_t mp_bma400_get_accel()
+{
+	struct acceleration values;
+	int ret = epic_bma400_get_accel(&values);
+
+	if (ret < 0) {
+		mp_raise_OSError(-ret);
+	}
+
+	mp_obj_t values_list[] = {
+		mp_obj_new_float(values.x),
+		mp_obj_new_float(values.y),
+		mp_obj_new_float(values.z),
+	};
+	return mp_obj_new_tuple(3, values_list);
+}
+static MP_DEFINE_CONST_FUN_OBJ_0(bma400_get_accel_obj, mp_bma400_get_accel);
+
+static const mp_rom_map_elem_t bma400_module_globals_table[] = {
+	{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_bma400) },
+	{ MP_ROM_QSTR(MP_QSTR_get_accel), MP_ROM_PTR(&bma400_get_accel_obj) },
+};
+static MP_DEFINE_CONST_DICT(bma400_module_globals, bma400_module_globals_table);
+
+const mp_obj_module_t bma400_module = {
+	.base    = { &mp_type_module },
+	.globals = (mp_obj_dict_t *)&bma400_module_globals,
+};
+
+/* Register the module to make it available in Python */
+MP_REGISTER_MODULE(MP_QSTR_bma400, bma400_module, MODULE_BMA400_ENABLED);
diff --git a/pycardium/modules/qstrdefs.h b/pycardium/modules/qstrdefs.h
index dedc57c0..b57dc36e 100644
--- a/pycardium/modules/qstrdefs.h
+++ b/pycardium/modules/qstrdefs.h
@@ -4,6 +4,10 @@
 #define Q(x)
 #endif
 
+/* bma400 */
+Q(bma400)
+Q(get_accel)
+
 /* leds */
 Q(leds)
 Q(BOTTOM_LEFT)
diff --git a/pycardium/mpconfigport.h b/pycardium/mpconfigport.h
index 233c01bb..0ae791f0 100644
--- a/pycardium/mpconfigport.h
+++ b/pycardium/mpconfigport.h
@@ -40,6 +40,7 @@
 /* Modules */
 #define MODULE_UTIME_ENABLED                (1)
 #define MODULE_LEDS_ENABLED                 (1)
+#define MODULE_BMA400_ENABLED               (1)
 #define MODULE_VIBRA_ENABLED                (1)
 #define MODULE_INTERRUPT_ENABLED            (1)
 #define MODULE_DISPLAY_ENABLED              (1)
-- 
GitLab