diff --git a/CMakeLists.txt b/CMakeLists.txt index 8b852bd..c75d401 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,8 +13,10 @@ add_subdirectory(drivers) target_sources(app PRIVATE src/main.c src/battery_module.c + src/encoder_module.c src/keyboard_core_module.c src/usb_hid_module.c + src/events/encoder_event.c src/events/hid_led_event.c src/mode_switch_module.c src/events/keyboard_hid_report_event.c diff --git a/boards/atguigu/mini_keyboard/mini_keyboard-pinctrl.dtsi b/boards/atguigu/mini_keyboard/mini_keyboard-pinctrl.dtsi index 85e9a33..a3833a2 100644 --- a/boards/atguigu/mini_keyboard/mini_keyboard-pinctrl.dtsi +++ b/boards/atguigu/mini_keyboard/mini_keyboard-pinctrl.dtsi @@ -13,4 +13,21 @@ low-power-enable; }; }; + + encoder_default: encoder_default { + group1 { + psels = , + ; + bias-pull-up; + }; + }; + + encoder_sleep: encoder_sleep { + group1 { + psels = , + ; + low-power-enable; + bias-pull-up; + }; + }; }; diff --git a/boards/atguigu/mini_keyboard/mini_keyboard.dts b/boards/atguigu/mini_keyboard/mini_keyboard.dts index 75afc76..9cc465c 100644 --- a/boards/atguigu/mini_keyboard/mini_keyboard.dts +++ b/boards/atguigu/mini_keyboard/mini_keyboard.dts @@ -15,6 +15,7 @@ aliases { led0 = &myled0; + qdec0 = &qdec; }; hid_kbd: hid_kbd { @@ -147,6 +148,16 @@ status = "okay"; }; +&qdec { + status = "okay"; + pinctrl-0 = <&encoder_default>; + pinctrl-1 = <&encoder_sleep>; + pinctrl-names = "default", "sleep"; + led-pre = <0>; + steps = <80>; + nordic,period = "SAMPLEPER_1024US"; +}; + &usbd { status = "okay"; num-bidir-endpoints = <0>; diff --git a/dts/bindings/vendor-prefixes.txt b/dts/bindings/vendor-prefixes.txt index 262ee04..e8443e8 100644 --- a/dts/bindings/vendor-prefixes.txt +++ b/dts/bindings/vendor-prefixes.txt @@ -1 +1,2 @@ +atguigu Atguigu injoinic Injoinic diff --git a/inc/events/encoder_event.h b/inc/events/encoder_event.h new file mode 100644 index 0000000..f823585 --- /dev/null +++ b/inc/events/encoder_event.h @@ -0,0 +1,22 @@ +#ifndef BLINKY_ENCODER_EVENT_H_ +#define BLINKY_ENCODER_EVENT_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct encoder_event { + struct app_event_header header; + int8_t detents; +}; + +APP_EVENT_TYPE_DECLARE(encoder_event); + +#ifdef __cplusplus +} +#endif + +#endif /* BLINKY_ENCODER_EVENT_H_ */ diff --git a/prj.conf b/prj.conf index 5884836..80d1d51 100644 --- a/prj.conf +++ b/prj.conf @@ -5,6 +5,8 @@ CONFIG_GPIO=y CONFIG_I2C=y CONFIG_NRFX_RTC2=y CONFIG_NRFX_GPPI=y +CONFIG_NRFX_QDEC=y +CONFIG_PINCTRL_DYNAMIC=y CONFIG_REBOOT=y CONFIG_SENSOR=y CONFIG_ADC=y diff --git a/src/encoder_module.c b/src/encoder_module.c new file mode 100644 index 0000000..40e669a --- /dev/null +++ b/src/encoder_module.c @@ -0,0 +1,226 @@ +#include +#include + +#include + +#define MODULE encoder_module +#include +#include + +#include +#include +#include +#include + +#include "encoder_event.h" + +LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF); + +#define ENCODER_QDEC_NODE DT_ALIAS(qdec0) +#define ENCODER_PULSES_PER_DETENT 4LL +#define ENCODER_DETENT_UDEG (360000000LL / (80LL / ENCODER_PULSES_PER_DETENT)) + +BUILD_ASSERT(DT_NODE_EXISTS(ENCODER_QDEC_NODE), "Missing qdec0 alias"); + +static const struct device *const qdec_dev = DEVICE_DT_GET(ENCODER_QDEC_NODE); + +static struct k_work encoder_report_work; +static struct sensor_trigger encoder_trigger = { + .type = SENSOR_TRIG_DATA_READY, + .chan = SENSOR_CHAN_ROTATION, +}; + +static bool initialized; +static bool running; +static int64_t angle_remainder_udeg; + +static int64_t sensor_value_to_udeg(const struct sensor_value *value) +{ + return ((int64_t)value->val1 * 1000000LL) + value->val2; +} + +static void submit_detents(int32_t detents) +{ + while (detents != 0) { + struct encoder_event *event = new_encoder_event(); + + if (detents > INT8_MAX) { + event->detents = INT8_MAX; + detents -= INT8_MAX; + } else if (detents < INT8_MIN) { + event->detents = INT8_MIN; + detents -= INT8_MIN; + } else { + event->detents = (int8_t)detents; + detents = 0; + } + + APP_EVENT_SUBMIT(event); + } +} + +static void encoder_report_work_handler(struct k_work *work) +{ + struct sensor_value rotation; + int64_t total_udeg; + int32_t detents; + int err; + + ARG_UNUSED(work); + + if (!running) { + return; + } + + err = sensor_sample_fetch(qdec_dev); + if (err) { + LOG_WRN("QDEC sample fetch failed (%d)", err); + return; + } + + err = sensor_channel_get(qdec_dev, SENSOR_CHAN_ROTATION, &rotation); + if (err) { + LOG_WRN("QDEC channel get failed (%d)", err); + return; + } + + total_udeg = angle_remainder_udeg + sensor_value_to_udeg(&rotation); + detents = (int32_t)(total_udeg / ENCODER_DETENT_UDEG); + angle_remainder_udeg = total_udeg - ((int64_t)detents * ENCODER_DETENT_UDEG); + + if (detents != 0) { + submit_detents(detents); + } +} + +static void encoder_trigger_handler(const struct device *dev, + const struct sensor_trigger *trigger) +{ + ARG_UNUSED(dev); + ARG_UNUSED(trigger); + + if (!running) { + return; + } + + k_work_submit(&encoder_report_work); +} + +static int module_init(void) +{ + int err; + + if (!device_is_ready(qdec_dev)) { + LOG_ERR("QDEC device not ready"); + return -ENODEV; + } + + k_work_init(&encoder_report_work, encoder_report_work_handler); + angle_remainder_udeg = 0; + + err = sensor_trigger_set(qdec_dev, &encoder_trigger, encoder_trigger_handler); + if (err) { + LOG_ERR("Cannot set QDEC trigger (%d)", err); + return err; + } + + return 0; +} + +static int module_start(void) +{ + int err; + + if (running) { + return 0; + } + + err = pm_device_action_run(qdec_dev, PM_DEVICE_ACTION_RESUME); + if (err && (err != -EALREADY) && (err != -ENOTSUP)) { + LOG_ERR("Cannot resume QDEC device (%d)", err); + return err; + } + + angle_remainder_udeg = 0; + running = true; + + return 0; +} + +static void module_pause(void) +{ + int err; + + if (!running) { + return; + } + + err = pm_device_action_run(qdec_dev, PM_DEVICE_ACTION_SUSPEND); + if (err && (err != -EALREADY) && (err != -ENOTSUP)) { + LOG_WRN("Cannot suspend QDEC device (%d)", err); + } + + angle_remainder_udeg = 0; + running = false; +} + +static bool app_event_handler(const struct app_event_header *aeh) +{ + if (is_module_state_event(aeh)) { + const struct module_state_event *event = cast_module_state_event(aeh); + + if (check_state(event, MODULE_ID(main), MODULE_STATE_READY)) { + int err; + + if (!initialized) { + err = module_init(); + if (err) { + module_set_state(MODULE_STATE_ERROR); + return false; + } + + initialized = true; + } + + err = module_start(); + if (err) { + module_set_state(MODULE_STATE_ERROR); + } else { + module_set_state(MODULE_STATE_READY); + } + } + + return false; + } + + if (is_power_down_event(aeh)) { + if (initialized) { + module_pause(); + module_set_state(MODULE_STATE_STANDBY); + } + + return false; + } + + if (is_wake_up_event(aeh)) { + if (initialized) { + int err = module_start(); + + if (err) { + module_set_state(MODULE_STATE_ERROR); + } else { + module_set_state(MODULE_STATE_READY); + } + } + + return false; + } + + __ASSERT_NO_MSG(false); + return false; +} + +APP_EVENT_LISTENER(MODULE, app_event_handler); +APP_EVENT_SUBSCRIBE(MODULE, module_state_event); +APP_EVENT_SUBSCRIBE_EARLY(MODULE, power_down_event); +APP_EVENT_SUBSCRIBE(MODULE, wake_up_event); diff --git a/src/events/encoder_event.c b/src/events/encoder_event.c new file mode 100644 index 0000000..a736b53 --- /dev/null +++ b/src/events/encoder_event.c @@ -0,0 +1,27 @@ +#include "encoder_event.h" + +static void log_encoder_event(const struct app_event_header *aeh) +{ + const struct encoder_event *event = cast_encoder_event(aeh); + + APP_EVENT_MANAGER_LOG(aeh, "detents:%d", event->detents); +} + +static void profile_encoder_event(struct log_event_buf *buf, + const struct app_event_header *aeh) +{ + const struct encoder_event *event = cast_encoder_event(aeh); + + nrf_profiler_log_encode_int8(buf, event->detents); +} + +APP_EVENT_INFO_DEFINE(encoder_event, + ENCODE(NRF_PROFILER_ARG_S8), + ENCODE("detents"), + profile_encoder_event); + +APP_EVENT_TYPE_DEFINE(encoder_event, + log_encoder_event, + &encoder_event_info, + APP_EVENT_FLAGS_CREATE( + APP_EVENT_TYPE_FLAGS_INIT_LOG_ENABLE));