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:
@@ -4,8 +4,11 @@ find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
|
||||
project(new_kbd)
|
||||
|
||||
zephyr_include_directories(${CMAKE_CURRENT_SOURCE_DIR}/inc)
|
||||
zephyr_include_directories(${CMAKE_CURRENT_SOURCE_DIR}/src/events)
|
||||
|
||||
target_sources(app PRIVATE
|
||||
src/main.c
|
||||
src/events/mode_event.c
|
||||
src/modules/button_map_module.c
|
||||
src/modules/mode_switch_module.c
|
||||
)
|
||||
|
||||
12
app.overlay
12
app.overlay
@@ -1,4 +1,9 @@
|
||||
/{};
|
||||
/ {
|
||||
zephyr,user {
|
||||
io-channels = <&adc 5>, <&adc 7>;
|
||||
};
|
||||
};
|
||||
|
||||
&gpio0 {
|
||||
status = "okay";
|
||||
};
|
||||
@@ -14,3 +19,8 @@
|
||||
&led_0 {
|
||||
status = "okay";
|
||||
};
|
||||
|
||||
/* 使能 SAADC,mode_switch_module 使用 channel 7 采样模式拨码电压。 */
|
||||
&adc {
|
||||
status = "okay";
|
||||
};
|
||||
|
||||
2
prj.conf
2
prj.conf
@@ -17,3 +17,5 @@ CONFIG_CAF_BUTTONS=y
|
||||
CONFIG_CAF_BUTTONS_DEF_PATH="buttons_def.h"
|
||||
CONFIG_CAF_BUTTONS_SCAN_INTERVAL=5
|
||||
CONFIG_CAF_BUTTONS_DEBOUNCE_INTERVAL=10
|
||||
|
||||
CONFIG_ADC=y
|
||||
|
||||
34
src/events/mode_event.c
Normal file
34
src/events/mode_event.c
Normal 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
23
src/events/mode_event.h
Normal 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
|
||||
237
src/modules/mode_switch_module.c
Normal file
237
src/modules/mode_switch_module.c
Normal 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);
|
||||
Reference in New Issue
Block a user