feat: 添加模式切换模块支持多协议键盘

添加了完整的模式切换功能,通过ADC采样检测模式拨码开关,
实现USB、BLE和2.4G三种工作模式的自动识别和切换。

- 新增mode_event事件用于传递模式状态
- 实现mode_switch_module模块,包含ADC初始化、
  模式识别算法和状态管理逻辑
- 配置CMakeLists.txt添加新源文件和头文件目录
- 更新设备树配置启用ADC和IO通道
- 添加Kconfig选项CONFIG_ADC=y
- 实现防抖机制和稳定的模式检测逻辑
- 集成到CAF事件系统,支持电源管理状态切换
This commit is contained in:
2026-03-11 10:44:50 +08:00
parent 3d9ce9168f
commit 86af0d2373
6 changed files with 311 additions and 2 deletions

View File

@@ -4,8 +4,11 @@ find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(new_kbd) project(new_kbd)
zephyr_include_directories(${CMAKE_CURRENT_SOURCE_DIR}/inc) zephyr_include_directories(${CMAKE_CURRENT_SOURCE_DIR}/inc)
zephyr_include_directories(${CMAKE_CURRENT_SOURCE_DIR}/src/events)
target_sources(app PRIVATE target_sources(app PRIVATE
src/main.c src/main.c
src/events/mode_event.c
src/modules/button_map_module.c src/modules/button_map_module.c
src/modules/mode_switch_module.c
) )

View File

@@ -1,4 +1,9 @@
/{}; / {
zephyr,user {
io-channels = <&adc 5>, <&adc 7>;
};
};
&gpio0 { &gpio0 {
status = "okay"; status = "okay";
}; };
@@ -13,4 +18,9 @@
&led_0 { &led_0 {
status = "okay"; status = "okay";
}; };
/* 使能 SAADCmode_switch_module 使用 channel 7 采样模式拨码电压。 */
&adc {
status = "okay";
};

View File

@@ -17,3 +17,5 @@ CONFIG_CAF_BUTTONS=y
CONFIG_CAF_BUTTONS_DEF_PATH="buttons_def.h" CONFIG_CAF_BUTTONS_DEF_PATH="buttons_def.h"
CONFIG_CAF_BUTTONS_SCAN_INTERVAL=5 CONFIG_CAF_BUTTONS_SCAN_INTERVAL=5
CONFIG_CAF_BUTTONS_DEBOUNCE_INTERVAL=10 CONFIG_CAF_BUTTONS_DEBOUNCE_INTERVAL=10
CONFIG_ADC=y

34
src/events/mode_event.c Normal file
View File

@@ -0,0 +1,34 @@
#include "mode_event.h"
static const char *const mode_name[] = {
[MODE_TYPE_USB] = "USB",
[MODE_TYPE_BLE] = "BLE",
[MODE_TYPE_2G4] = "2.4G",
};
static void log_mode_event(const struct app_event_header *aeh)
{
const struct mode_event *event = cast_mode_event(aeh);
__ASSERT_NO_MSG(event->mode_type < MODE_TYPE_COUNT);
APP_EVENT_MANAGER_LOG(aeh, "mode=%s(%u)", mode_name[event->mode_type], event->mode_type);
}
static void profile_mode_event(struct log_event_buf *buf,
const struct app_event_header *aeh)
{
const struct mode_event *event = cast_mode_event(aeh);
nrf_profiler_log_encode_uint8(buf, (uint8_t)event->mode_type);
}
APP_EVENT_INFO_DEFINE(mode_event,
ENCODE(NRF_PROFILER_ARG_U8),
ENCODE("mode"),
profile_mode_event);
APP_EVENT_TYPE_DEFINE(mode_event,
log_mode_event,
&mode_event_info,
APP_EVENT_FLAGS_CREATE(APP_EVENT_TYPE_FLAGS_INIT_LOG_ENABLE));

23
src/events/mode_event.h Normal file
View File

@@ -0,0 +1,23 @@
#ifndef MODE_EVENT_H
#define MODE_EVENT_H
#include <app_event_manager.h>
#include <app_event_manager_profiler_tracer.h>
typedef enum
{
MODE_TYPE_USB,
MODE_TYPE_BLE,
MODE_TYPE_2G4,
MODE_TYPE_COUNT,
} mode_type_t;
struct mode_event
{
struct app_event_header header;
mode_type_t mode_type;
};
APP_EVENT_TYPE_DECLARE(mode_event);
#endif

View File

@@ -0,0 +1,237 @@
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/adc.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/sys/atomic.h>
#include <zephyr/sys/util.h>
#include <stdlib.h>
#include <app_event_manager.h>
#include <caf/events/power_event.h>
#include <caf/events/keep_alive_event.h>
#define MODULE mode_switch
#include <caf/events/module_state_event.h>
#include "mode_event.h"
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(MODULE);
#define MODE_USER_NODE DT_PATH(zephyr_user)
#define MODE_ADC_IO_CH_IDX 0
#define MODE_SAMPLE_INTERVAL_MS 50
static const struct adc_dt_spec mode_adc =
ADC_DT_SPEC_GET_BY_IDX(MODE_USER_NODE, MODE_ADC_IO_CH_IDX);
static struct k_work_delayable mode_sample_work;
static int16_t adc_sample_buffer;
static atomic_t active;
static mode_type_t current_mode;
static uint8_t mode_stable_status;
static int init_adc(void)
{
if (!adc_is_ready_dt(&mode_adc))
{
LOG_ERR("ADC device not ready");
return -ENODEV;
}
int err = adc_channel_setup_dt(&mode_adc);
if (err)
{
LOG_ERR("ADC channel setup failed (err=%d)", err);
return err;
}
return 0;
}
static mode_type_t classify_mode_from_mv(int32_t mv)
{
/*
* 使用“距离最近中心点”的分类方式,避免阈值边界附近抖动时出现模式跳变。
* 三个中心点直接对应硬件设计目标电压USB=0mV、2.4G=1650mV、BLE=3300mV。
*/
static const int32_t centers[MODE_TYPE_COUNT] = {
[MODE_TYPE_USB] = 0,
[MODE_TYPE_BLE] = 3300,
[MODE_TYPE_2G4] = 1650,
};
int32_t best_diff = INT32_MAX;
mode_type_t best_mode = MODE_TYPE_USB;
for (size_t i = 0; i < MODE_TYPE_COUNT; i++)
{
int32_t diff = abs(mv - centers[i]);
if (diff < best_diff)
{
best_diff = diff;
best_mode = (mode_type_t)i;
}
}
return best_mode;
}
static int read_mode(mode_type_t *mode)
{
struct adc_sequence sequence = {0};
int err = adc_sequence_init_dt(&mode_adc, &sequence);
if (err)
{
return err;
}
sequence.buffer = &adc_sample_buffer;
sequence.buffer_size = sizeof(adc_sample_buffer);
err = adc_read_dt(&mode_adc, &sequence);
if (err)
{
return err;
}
int32_t v = adc_sample_buffer;
err = adc_raw_to_millivolts_dt(&mode_adc, &v);
if (err)
{
return err;
}
*mode = classify_mode_from_mv(v);
return 0;
}
static void publish_mode_event(mode_type_t mode)
{
if (current_mode == mode)
return;
current_mode = mode;
struct mode_event *event = new_mode_event();
event->mode_type = mode;
APP_EVENT_SUBMIT(event);
/*
* 模式切换是明确的人机交互动作。这里同步上报 keep_alive_event
* 让 power manager 重置休眠倒计时,避免用户刚切换模式就进入省电流程。
*/
keep_alive();
}
static void mode_sample_fn(struct k_work *work)
{
ARG_UNUSED(work);
if (!atomic_get(&active))
{
return;
}
mode_type_t sampled_mode;
int err = read_mode(&sampled_mode);
if (err)
{
LOG_ERR("ADC read failed (err=%d)", err);
module_set_state(MODULE_STATE_ERROR);
return;
}
mode_stable_status = mode_stable_status << 2;
mode_stable_status |= (sampled_mode & 0x3);
switch (mode_stable_status)
{
case 0b00000000:
publish_mode_event(MODE_TYPE_USB);
break;
case 0b01010101:
publish_mode_event(MODE_TYPE_BLE);
break;
case 0b10101010:
publish_mode_event(MODE_TYPE_2G4);
break;
default:
break;
}
k_work_reschedule(&mode_sample_work, K_MSEC(MODE_SAMPLE_INTERVAL_MS));
}
static void mode_switch_suspend(void)
{
atomic_set(&active, false);
(void)k_work_cancel_delayable(&mode_sample_work);
module_set_state(MODULE_STATE_STANDBY);
}
static void mode_switch_resume(void)
{
if (atomic_get(&active))
return;
atomic_set(&active, true);
k_work_reschedule(&mode_sample_work, K_NO_WAIT);
module_set_state(MODULE_STATE_READY);
}
static void init_mode_switch(void)
{
if (atomic_get(&active))
return;
if (init_adc())
{
module_set_state(MODULE_STATE_ERROR);
return;
}
mode_stable_status = 0xFF;
current_mode = MODE_TYPE_COUNT;
atomic_set(&active, false);
k_work_init_delayable(&mode_sample_work, mode_sample_fn);
mode_switch_resume();
}
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))
{
init_mode_switch();
}
return false;
}
if (is_power_down_event(aeh))
{
mode_switch_suspend();
return false;
}
if (is_wake_up_event(aeh))
{
mode_switch_resume();
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);