From 01c6b90352617cc3375f58a77fb3773958a40fb0 Mon Sep 17 00:00:00 2001
From: Adrian <gitlab@ardy.io>
Date: Mon, 22 Jul 2019 23:26:27 +0000
Subject: [PATCH] feat(modules): Add light-sensor module

---
 epicardium/epicardium.h           | 41 +++++++++++++++++
 epicardium/modules/light_sensor.c | 76 +++++++++++++++++++++++++++++++
 epicardium/modules/meson.build    |  1 +
 pycardium/meson.build             |  1 +
 pycardium/modules/light_sensor.c  | 60 ++++++++++++++++++++++++
 pycardium/modules/qstrdefs.h      |  6 +++
 pycardium/mpconfigport.h          |  1 +
 7 files changed, 186 insertions(+)
 create mode 100644 epicardium/modules/light_sensor.c
 create mode 100644 pycardium/modules/light_sensor.c

diff --git a/epicardium/epicardium.h b/epicardium/epicardium.h
index cf7a7f35..470a4185 100644
--- a/epicardium/epicardium.h
+++ b/epicardium/epicardium.h
@@ -35,6 +35,9 @@ typedef unsigned int size_t;
 #define API_STREAM_READ        0x6
 #define API_INTERRUPT_ENABLE   0x7
 #define API_INTERRUPT_DISABLE  0x8
+#define API_LIGHT_SENSOR_RUN   0x9
+#define API_LIGHT_SENSOR_GET   0xa
+#define API_LIGHT_SENSOR_STOP  0xb
 
 #define API_DISP_OPEN          0x10
 #define API_DISP_CLOSE         0x11
@@ -398,4 +401,42 @@ API(API_DISP_CIRC,
 	    uint16_t pixelsize)
     );
 
+/**
+ * Start continuous readout of the light sensor. Will read light level
+ * at preconfigured interval and make it available via `epic_light_sensor_get()`.
+ *
+ * If the continuous readout was already running, this function will silently pass.
+ *
+ *
+ * :return: `0` if the start was successful or a negative error value
+ *      if an error occured. Possible errors:
+ *
+ *      - ``-EBUSY``: The timer could not be scheduled.
+ */
+API(API_LIGHT_SENSOR_RUN, int epic_light_sensor_run());
+
+/**
+ * Get the last light level measured by the continuous readout.
+ *
+ * :param uint16_t* value: where the last light level should be written.
+ * :return: `0` if the readout was successful or a negative error
+ *      value. Possible errors:
+ *
+ *      - ``-ENODATA``: Continuous readout not currently running.
+ */
+API(API_LIGHT_SENSOR_GET, int epic_light_sensor_get(uint16_t* value));
+
+
+/**
+ * Stop continuous readout of the light sensor.
+ *
+ * If the continuous readout wasn't running, this function will silently pass.
+ *
+ * :return: `0` if the stop was sucessful or a negative error value
+ *      if an error occured. Possible errors:
+ *
+ *      - ``-EBUSY``: The timer stop could not be scheduled.
+ */
+API(API_LIGHT_SENSOR_STOP, int epic_light_sensor_stop());
+
 #endif /* _EPICARDIUM_H */
diff --git a/epicardium/modules/light_sensor.c b/epicardium/modules/light_sensor.c
new file mode 100644
index 00000000..ea5c4ccc
--- /dev/null
+++ b/epicardium/modules/light_sensor.c
@@ -0,0 +1,76 @@
+#include "FreeRTOS.h"
+#include "timers.h"
+#include "led.h"
+#include "mxc_config.h"
+#include "adc.h"
+#include "gpio.h"
+#include <errno.h>
+
+#define READ_FREQ pdMS_TO_TICKS(100)
+
+static uint16_t last_value;
+static TimerHandle_t poll_timer;
+static StaticTimer_t poll_timer_buffer;
+
+int epic_light_sensor_init()
+{
+	const sys_cfg_adc_t sys_adc_cfg =
+		NULL; /* No system specific configuration needed. */
+	if (ADC_Init(0x9, &sys_adc_cfg) != E_NO_ERROR) {
+		return -EINVAL;
+	}
+	GPIO_Config(&gpio_cfg_adc7);
+	return 0;
+}
+
+void readAdcCallback()
+{
+	ADC_StartConvert(ADC_CH_7, 0, 0);
+	ADC_GetData(&last_value);
+}
+
+int epic_light_sensor_run()
+{
+	epic_light_sensor_init();
+
+	if (!poll_timer) {
+		poll_timer = xTimerCreateStatic(
+			"light_sensor_adc",
+			READ_FREQ,
+			pdTRUE,
+			NULL,
+			readAdcCallback,
+			&poll_timer_buffer
+		);
+		// since &poll_timer_buffer is not NULL, xTimerCreateStatic should allways succeed, so
+		// we don't need to check for poll_timer being NULL.
+	}
+	if (xTimerIsTimerActive(poll_timer) == pdFALSE) {
+		if (xTimerStart(poll_timer, 0) != pdPASS) {
+			return -EBUSY;
+		}
+	}
+	return 0;
+}
+
+int epic_light_sensor_stop()
+{
+	if (!poll_timer || xTimerIsTimerActive(poll_timer) == pdFALSE) {
+		// timer wasn't running (or never started), just silently pass
+		return 0;
+	}
+
+	if (xTimerStop(poll_timer, 0) != pdPASS) {
+		return -EBUSY;
+	}
+	return 0;
+}
+
+int epic_light_sensor_get(uint16_t *value)
+{
+	if (!poll_timer || !xTimerIsTimerActive(poll_timer)) {
+		return -ENODATA;
+	}
+	*value = last_value;
+	return 0;
+}
diff --git a/epicardium/modules/meson.build b/epicardium/modules/meson.build
index d552623e..68c1b9d9 100644
--- a/epicardium/modules/meson.build
+++ b/epicardium/modules/meson.build
@@ -7,4 +7,5 @@ module_sources = files(
   'serial.c',
   'stream.c',
   'vibra.c',
+  'light_sensor.c',
 )
diff --git a/pycardium/meson.build b/pycardium/meson.build
index ad48e91c..895be888 100644
--- a/pycardium/meson.build
+++ b/pycardium/meson.build
@@ -6,6 +6,7 @@ modsrc = files(
   'modules/sys_display.c',
   'modules/utime.c',
   'modules/vibra.c',
+  'modules/light_sensor.c'
 )
 
 #################################
diff --git a/pycardium/modules/light_sensor.c b/pycardium/modules/light_sensor.c
new file mode 100644
index 00000000..39f612a9
--- /dev/null
+++ b/pycardium/modules/light_sensor.c
@@ -0,0 +1,60 @@
+#include "py/obj.h"
+#include "py/runtime.h"
+#include "py/builtin.h"
+#include "epicardium.h"
+
+STATIC mp_obj_t mp_light_sensor_start()
+{
+	int status = epic_light_sensor_run();
+	if (status == -EBUSY) {
+		mp_raise_msg(
+			&mp_type_RuntimeError, "timer could not be scheduled"
+		);
+	}
+	return mp_const_none;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_0(light_sensor_start_obj, mp_light_sensor_start);
+
+STATIC mp_obj_t mp_light_sensor_get_reading()
+{
+	uint16_t last;
+	int status = epic_light_sensor_get(&last);
+	if (status == -ENODATA) {
+		mp_raise_ValueError("sensor not running");
+		return mp_const_none;
+	}
+	return mp_obj_new_int_from_uint(last);
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_0(
+	light_sensor_get_obj, mp_light_sensor_get_reading
+);
+
+STATIC mp_obj_t mp_light_sensor_stop()
+{
+	int status = epic_light_sensor_stop();
+	if (status == -EBUSY) {
+		mp_raise_msg(
+			&mp_type_RuntimeError, "timer could not be stopped"
+		);
+	}
+	return mp_const_none;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_0(light_sensor_stop_obj, mp_light_sensor_stop);
+
+STATIC const mp_rom_map_elem_t light_sensor_module_globals_table[] = {
+	{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_light_sensor) },
+	{ MP_ROM_QSTR(MP_QSTR_start), MP_ROM_PTR(&light_sensor_start_obj) },
+	{ MP_ROM_QSTR(MP_QSTR_stop), MP_ROM_PTR(&light_sensor_stop_obj) },
+	{ MP_ROM_QSTR(MP_QSTR_get_reading), MP_ROM_PTR(&light_sensor_get_obj) }
+};
+STATIC MP_DEFINE_CONST_DICT(
+	light_sensor_module_globals, light_sensor_module_globals_table
+);
+
+const mp_obj_module_t light_sensor_module = {
+	.base    = { &mp_type_module },
+	.globals = (mp_obj_dict_t *)&light_sensor_module_globals,
+};
+
+/* clang-format off */
+MP_REGISTER_MODULE(MP_QSTR_light_sensor, light_sensor_module, MODULE_LIGHT_SENSOR_ENABLED);
diff --git a/pycardium/modules/qstrdefs.h b/pycardium/modules/qstrdefs.h
index 707a27af..3dda54d6 100644
--- a/pycardium/modules/qstrdefs.h
+++ b/pycardium/modules/qstrdefs.h
@@ -40,3 +40,9 @@ Q(line)
 Q(rect)
 Q(circ)
 Q(clear)
+
+/* ambient */
+Q(light_sensor)
+Q(start)
+Q(get_reading)
+Q(stop)
diff --git a/pycardium/mpconfigport.h b/pycardium/mpconfigport.h
index b348861a..6bfb2c67 100644
--- a/pycardium/mpconfigport.h
+++ b/pycardium/mpconfigport.h
@@ -42,6 +42,7 @@
 #define MODULE_VIBRA_ENABLED                (1)
 #define MODULE_INTERRUPT_ENABLED            (1)
 #define MODULE_DISPLAY_ENABLED              (1)
+#define MODULE_LIGHT_SENSOR_ENABLED         (1)
 
 /*
  * This port is intended to be 32-bit, but unfortunately, int32_t for
-- 
GitLab