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)
|
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
|
||||||
)
|
)
|
||||||
|
|||||||
14
app.overlay
14
app.overlay
@@ -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";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* 使能 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_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
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