diff --git a/CMakeLists.txt b/CMakeLists.txt index e2c0eba..26662f5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,10 +4,15 @@ cmake_minimum_required(VERSION 3.20.0) find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) project(blinky) -zephyr_include_directories(inc) +zephyr_include_directories( + inc + inc/events +) add_subdirectory(drivers) target_sources(app PRIVATE src/main.c src/battery_module.c + src/mode_switch_module.c + src/events/mode_switch_event.c ) diff --git a/boards/atguigu/mini_keyboard/mini_keyboard.dts b/boards/atguigu/mini_keyboard/mini_keyboard.dts index bc2cb9e..9e5b20a 100644 --- a/boards/atguigu/mini_keyboard/mini_keyboard.dts +++ b/boards/atguigu/mini_keyboard/mini_keyboard.dts @@ -32,6 +32,13 @@ power-gpios = <&gpio0 9 GPIO_ACTIVE_HIGH>; power-on-sample-delay-us = <200>; }; + + mode_switch_adc: mode-switch-adc { + compatible = "voltage-divider"; + io-channels = <&adc 5>; + output-ohms = <1>; + full-ohms = <1>; + }; }; &flash0 { @@ -71,6 +78,16 @@ #address-cells = <1>; #size-cells = <0>; + channel@5 { + reg = <5>; + zephyr,gain = "ADC_GAIN_1_6"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,acquisition-time = ; + zephyr,input-positive = ; + zephyr,resolution = <12>; + zephyr,oversampling = <4>; + }; + channel@7 { reg = <7>; zephyr,gain = "ADC_GAIN_1_4"; diff --git a/inc/events/mode_switch_event.h b/inc/events/mode_switch_event.h new file mode 100644 index 0000000..7ebf58c --- /dev/null +++ b/inc/events/mode_switch_event.h @@ -0,0 +1,29 @@ +#ifndef BLINKY_MODE_SWITCH_EVENT_H_ +#define BLINKY_MODE_SWITCH_EVENT_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +enum mode_switch_mode { + MODE_SWITCH_USB, + MODE_SWITCH_24G, + MODE_SWITCH_BLE, +}; + +struct mode_switch_event { + struct app_event_header header; + enum mode_switch_mode mode; + uint16_t voltage_mv; +}; + +APP_EVENT_TYPE_DECLARE(mode_switch_event); + +#ifdef __cplusplus +} +#endif + +#endif /* BLINKY_MODE_SWITCH_EVENT_H_ */ diff --git a/src/events/mode_switch_event.c b/src/events/mode_switch_event.c new file mode 100644 index 0000000..ce17ee7 --- /dev/null +++ b/src/events/mode_switch_event.c @@ -0,0 +1,45 @@ +#include + +#include "mode_switch_event.h" + +static const char *mode_name(enum mode_switch_mode mode) +{ + switch (mode) { + case MODE_SWITCH_USB: + return "USB"; + case MODE_SWITCH_24G: + return "2.4G"; + case MODE_SWITCH_BLE: + return "BLE"; + default: + return "?"; + } +} + +static void log_mode_switch_event(const struct app_event_header *aeh) +{ + const struct mode_switch_event *event = cast_mode_switch_event(aeh); + + APP_EVENT_MANAGER_LOG(aeh, "mode:%s voltage:%" PRIu16 "mV", + mode_name(event->mode), event->voltage_mv); +} + +static void profile_mode_switch_event(struct log_event_buf *buf, + const struct app_event_header *aeh) +{ + const struct mode_switch_event *event = cast_mode_switch_event(aeh); + + nrf_profiler_log_encode_uint8(buf, event->mode); + nrf_profiler_log_encode_uint16(buf, event->voltage_mv); +} + +APP_EVENT_INFO_DEFINE(mode_switch_event, + ENCODE(NRF_PROFILER_ARG_U8, NRF_PROFILER_ARG_U16), + ENCODE("mode", "voltage_mv"), + profile_mode_switch_event); + +APP_EVENT_TYPE_DEFINE(mode_switch_event, + log_mode_switch_event, + &mode_switch_event_info, + APP_EVENT_FLAGS_CREATE( + APP_EVENT_TYPE_FLAGS_INIT_LOG_ENABLE)); diff --git a/src/mode_switch_module.c b/src/mode_switch_module.c new file mode 100644 index 0000000..82891ac --- /dev/null +++ b/src/mode_switch_module.c @@ -0,0 +1,215 @@ +#include +#include + +#include + +#define MODULE mode_switch_module +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "mode_switch_event.h" + +LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF); + +#define MODE_SWITCH_ADC_NODE DT_NODELABEL(mode_switch_adc) +#define MODE_SWITCH_SAMPLE_INTERVAL K_MSEC(500) +#define MODE_SWITCH_USB_MAX_MV 825 +#define MODE_SWITCH_24G_MAX_MV 2475 + +BUILD_ASSERT(DT_NODE_EXISTS(MODE_SWITCH_ADC_NODE), + "Missing mode_switch_adc node in devicetree"); + +static const struct device *const mode_switch_adc_dev = + DEVICE_DT_GET(MODE_SWITCH_ADC_NODE); + +static struct k_work_delayable mode_switch_sample_work; +static bool initialized; +static bool running; +static bool force_report; +static bool mode_valid; +static enum mode_switch_mode last_mode; + +static enum mode_switch_mode detect_mode(uint16_t voltage_mv) +{ + if (voltage_mv < MODE_SWITCH_USB_MAX_MV) { + return MODE_SWITCH_USB; + } + + if (voltage_mv < MODE_SWITCH_24G_MAX_MV) { + return MODE_SWITCH_24G; + } + + return MODE_SWITCH_BLE; +} + +static int mode_switch_enable(bool enable) +{ + enum pm_device_action action = enable ? PM_DEVICE_ACTION_RESUME + : PM_DEVICE_ACTION_SUSPEND; + int err = pm_device_action_run(mode_switch_adc_dev, action); + + if (err && (err != -EALREADY) && (err != -ENOTSUP)) { + LOG_ERR("Cannot %s mode switch ADC (%d)", + enable ? "resume" : "suspend", err); + return err; + } + + return 0; +} + +static void mode_switch_sample_fn(struct k_work *work) +{ + struct sensor_value voltage; + int err; + + ARG_UNUSED(work); + + if (!running) { + return; + } + + err = sensor_sample_fetch(mode_switch_adc_dev); + if (err) { + LOG_WRN("Mode switch ADC sample fetch failed (%d)", err); + goto reschedule; + } + + err = sensor_channel_get(mode_switch_adc_dev, SENSOR_CHAN_VOLTAGE, &voltage); + if (err) { + LOG_WRN("Mode switch ADC channel get failed (%d)", err); + goto reschedule; + } + + uint16_t sample_mv = (uint16_t)((voltage.val1 * 1000) + (voltage.val2 / 1000)); + enum mode_switch_mode mode = detect_mode(sample_mv); + + if (force_report || !mode_valid || (mode != last_mode)) { + struct mode_switch_event *event = new_mode_switch_event(); + + event->mode = mode; + event->voltage_mv = sample_mv; + APP_EVENT_SUBMIT(event); + + last_mode = mode; + mode_valid = true; + force_report = false; + } + +reschedule: + if (running) { + k_work_reschedule(&mode_switch_sample_work, MODE_SWITCH_SAMPLE_INTERVAL); + } +} + +static int module_init(void) +{ + if (!device_is_ready(mode_switch_adc_dev)) { + LOG_ERR("Mode switch ADC device not ready"); + return -ENODEV; + } + + k_work_init_delayable(&mode_switch_sample_work, mode_switch_sample_fn); + mode_valid = false; + force_report = false; + + return 0; +} + +static int module_start(void) +{ + int err; + + if (running) { + return 0; + } + + err = mode_switch_enable(true); + if (err) { + return err; + } + + running = true; + force_report = true; + k_work_reschedule(&mode_switch_sample_work, K_NO_WAIT); + + return 0; +} + +static void module_pause(void) +{ + if (!running) { + return; + } + + (void)k_work_cancel_delayable(&mode_switch_sample_work); + (void)mode_switch_enable(false); + 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);