GD32F407VE天空星开发板的ADC按键(ADKey)的实现
GD32F407VE天空星开发板的ADC按键(ADKey)的实现
一、ADC按键概述
1.1 什么是ADC按键?
ADC按键(ADKey)是一种基于电压分压原理的多按键检测方案。与传统矩阵键盘相比,它只需要一个ADC引脚即可检测多个按键状态,特别适合IO引脚有限的MCU应用场景。
1.2 技术优势
| 优势 | 具体说明 |
|---|---|
| 省IO口 | 只需1个ADC引脚检测多个按键,适合IO紧张的MCU |
| 电路简单 | 无需复杂的行列扫描电路,布线简单 |
| 成本低 | 仅需电阻+按钮+ADC,BOM成本低 |
| 软件简单 | 不需要复杂扫描算法,只需判断ADC电压范围 |
1.3 硬件原理

分压原理表:
| 按键状态 | 等效电阻 | 理论电压值 | ADC值(12位) |
|---|---|---|---|
| 无按键 | 无 | 3.30V | 4095 |
| SW1 | R4 = 3kΩ | ≈ 0.75V | 940 |
| SW2 | R5 = 10kΩ | ≈ 1.65V | 2060 |
| SW3 | R6 = 33kΩ | ≈ 2.48V | 3140 |
| SW1+SW2 | R4//R5≈2.31kΩ | ≈ 0.61V | 770 |
| SW1+SW3 | R4//R6≈2.77kΩ | ≈ 0.68V | 880 |
| SW2+SW3 | R5//R6≈7.67kΩ | ≈ 1.43V | 1780 |
| 全按下 | R4//R5//R6≈2.15kΩ | ≈ 0.59V | 730 |
二、硬件连接
2.1 接线说明
//////////////////// ADKey ////////////////////
/* 接线说明
交互板 GD32F407K ===> PA2//可以接任意的adc采样引脚就好3v3 ===> 3v3GND ===> GND
*/
2.2 引脚配置
// IN2 PA2
#define ADKEY_RCU RCU_GPIOA
#define ADKEY_PIN GPIOA, GPIO_PIN_2
#define ADKEY_CHN ADC_CHANNEL_2
#define ADKEY_SAMPLETIME ADC_SAMPLETIME_84
三、ADC驱动实现
3.1 ADC配置
// ADC0.h
#define ADC0_CHNS_LEN 1 // 转换通道个数
#define ADC0_CHNS { ADC_CHANNEL_2 } // 通道列表
#define ADC0_CHNS_SAMPLES { ADC_SAMPLETIME_84 } // 采样时间// ADC0.c
void ADC0_init() {adc_deinit(); // 重置ADCadc_clock_config(ADC0_CLK); // 配置时钟// GPIO配置为模拟输入for(uint8_t i = 0; i < ADC0_CHNS_LEN; i++) { if(adc_chns[i] == ADC_CHANNEL_2) {GPIO_analog(CH2_PIN); // PA2配置为模拟输入}}// ADC基本配置adc_resolution_config(ADC0, ADC0_RESOLUTION); // 12位分辨率adc_data_alignment_config(ADC0, ADC_DATAALIGN_RIGHT); // 数据右对齐// 规则通道配置adc_channel_length_config(ADC0, ADC_ROUTINE_CHANNEL, ADC0_CHNS_LEN);for(uint8_t i = 0; i < ADC0_CHNS_LEN; i++) {adc_routine_channel_config(ADC0, i, adc_chns[i], adc_chns_samples[i]);}// 模式配置adc_special_function_config(ADC0, ADC_SCAN_MODE, ENABLE); // 扫描模式adc_special_function_config(ADC0, ADC_CONTINUOUS_MODE, DISABLE); // 非连续模式// DMA配置adc_dma_request_after_last_enable(ADC0);adc_dma_mode_enable(ADC0);// 启用ADCadc_enable(ADC0);adc_calibration_enable(ADC0); // 校准delay_1ms(CALIB_TIME);DMA_config(); // DMA配置
}
3.2 DMA配置
// ADC0.c
static void DMA_config() {rcu_periph_clock_enable(RCU_DMA1);dma_deinit(ADC0_DMA_CH);dma_single_data_parameter_struct init_struct;dma_single_data_para_struct_init(&init_struct);// DMA参数配置init_struct.direction = DMA_PERIPH_TO_MEMORY; // 外设到内存init_struct.periph_addr = ADC0_DMA_PERIPH_ADDR; // ADC数据寄存器地址init_struct.periph_inc = DMA_PERIPH_INCREASE_DISABLE;init_struct.memory0_addr = (uint32_t)g_recv_buff; // 内存缓冲区init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE;init_struct.periph_memory_width = DMA_PERIPH_WIDTH_16BIT;init_struct.number = ADC0_CHNS_LEN;init_struct.priority = DMA_PRIORITY_HIGH;init_struct.circular_mode = DMA_CIRCULAR_MODE_ENABLE; // 循环模式dma_single_data_mode_init(ADC0_DMA_CH, &init_struct);dma_channel_subperipheral_select(ADC0_DMA_CH, ADC0_DMA_SUB_PERIPH);dma_flag_clear(ADC0_DMA_CH, DMA_FLAG_FTF);dma_channel_enable(ADC0_DMA_CH); // 启动DMA
}
3.3 ADC数据读取
// ADC0.c
uint16_t ADC0_get(uint8_t index) {adc_software_trigger_enable(ADC0, ADC_ROUTINE_CHANNEL); // 软件触发转换// 等待DMA传输完成while(RESET == dma_flag_get(ADC0_DMA_CH, DMA_FLAG_FTF));dma_flag_clear(ADC0_DMA_CH, DMA_FLAG_FTF);return g_recv_buff[index]; // 返回采样值
}
四、按键检测算法
4.1 按键值定义
// bsp_adkey.c
// 按键按下时ADC的理论值
#define KEY1_VALUE 950
#define KEY2_VALUE 2060
#define KEY3_VALUE 3140
#define KEY1_KEY2_VALUE 770
#define KEY1_KEY3_VALUE 880
#define KEY2_KEY3_VALUE 1780
#define KEY1_KEY2_KEY3_VALUE 730
#define KEY_ALL_UP 4095#define KEY_RANGE 15 // 判断范围±15
#define KEY1 (1 << 0) // 按键1状态位
#define KEY2 (1 << 1) // 按键2状态位
#define KEY3 (1 << 2) // 按键3状态位// 判断ADC值是否在指定范围内
#define IS_IN(a, r) (((a) < ((r) + KEY_RANGE)) && ((a) > ((r) - KEY_RANGE)))
4.2 均值滤波算法
// 去极值平均滤波参数
#define ONT_CNT 2 // 每次采样次数
#define SAMPLE_CNT 40 // 总采样次数
#define NUM_CNT (SAMPLE_CNT/ONT_CNT) // 采样轮数void ADKey_scan() {static uint8_t i = 0;static uint32_t sum = 0;uint16_t value, adc;static uint16_t max_value = 0; // 最大值static uint16_t min_value = 4096; // 最小值uint8_t j;if (i < NUM_CNT) {// 多次采样for(j = 0; j < ONT_CNT; j++) {value = ADC0_get(0); // 获取ADC采样值sum += value;// 更新极值if (value > max_value) max_value = value;if (value < min_value) min_value = value;}i++;} else {// 计算去极值平均值sum = sum - max_value - min_value;adc = sum / (SAMPLE_CNT-2);// 重置采样状态i = 0;sum = 0;max_value = 0;min_value = 4096;// 按键状态检测key_state = 0; // 清空当前状态if (IS_IN(adc, KEY1_VALUE)) {key_state |= KEY1;} else if (IS_IN(adc, KEY2_VALUE)) {key_state |= KEY2;} else if (IS_IN(adc, KEY3_VALUE)) {key_state |= KEY3;} else if (IS_IN(adc, KEY1_KEY2_VALUE)) {key_state |= KEY1;key_state |= KEY2;} else if (IS_IN(adc, KEY1_KEY3_VALUE)) {key_state |= KEY1;key_state |= KEY3;} else if (IS_IN(adc, KEY2_KEY3_VALUE)) {key_state |= KEY2;key_state |= KEY3;} else if (IS_IN(adc, KEY1_KEY2_KEY3_VALUE)) {key_state |= KEY1;key_state |= KEY2;key_state |= KEY3;}// 状态变化检测uint8_t diff = key_state ^ last_state; // 异或检测变化位if (diff != 0) { // 状态有变化last_state = key_state; // 更新上次状态// 检测每个按键的状态变化if (diff & KEY1) {if (key_state & KEY1) {printf("key1 down\n");} else {printf("key1 up\n");}}if (diff & KEY2) {if (key_state & KEY2) {printf("key2 down\n");} else {printf("key2 up\n");}}if (diff & KEY3) {if (key_state & KEY3) {printf("key3 down\n");} else {printf("key3 up\n");}}}}
}
五、任务调度系统
5.1 任务配置
// Task.c
Task_t task_list[] = {{0, 500, App_debug_led188_test_task}, // 500ms:LED测试{0, 2, App_debug_led188_display_task}, // 2ms:LED显示刷新{0, 1, App_debug_adkey_scan_task}, // 1ms:按键扫描
};
5.2 按键扫描任务
// App_debug.c
void App_debug_adkey_scan_task() {ADKey_scan(); // 执行按键扫描
}
六、滤波算法详解
6.1 去极值平均滤波原理
算法步骤:
- 连续采样40次数据
- 找出最大值和最小值(极值)
- 去掉极值后求平均值
- 用平均值进行按键状态判断
优势:
- 有效消除偶发干扰
- 保持响应速度
- 计算量适中
6.2 ADC转换时间计算
| 阶段 | 作用 | 耗时 |
|---|---|---|
| 采样时间 | 信号稳定 | 84个ADC时钟周期 |
| 量化编码 | SAR转换 | 12个ADC时钟周期(固定) |
| 总时间 | 一次转换 | 96个ADC时钟周期 |
七、高级功能扩展
7.1 短按、长按、双击检测
虽然当前代码未实现,但可以基于现有框架扩展:
// 伪代码示例
typedef struct {uint32_t press_time; // 按下时间uint32_t release_time; // 释放时间 uint8_t click_count; // 点击次数uint8_t state; // 当前状态
} Key_Event_t;void ADCKey_on_click(uint8_t key); // 单击回调
void ADCKey_on_long_click(uint8_t key); // 长按回调
void ADCKey_on_double_click(uint8_t key); // 双击回调
7.2 状态机实现
// 按键状态机
typedef enum {KEY_IDLE, // 空闲状态KEY_PRESSED, // 已按下KEY_RELEASED, // 已释放KEY_LONG_PRESS, // 长按KEY_DOUBLE_WAIT // 等待第二次按下
} Key_State_t;
八、性能优化建议
8.1 采样率优化
// 根据应用场景调整采样参数
#define SAMPLE_CNT 20 // 降低采样次数提高响应速度
#define KEY_RANGE 20 // 增大判断范围提高容错性
8.2 功耗优化
// 动态调整采样频率
void adjust_sample_rate(uint8_t mode) {// 正常模式:高频采样// 休眠模式:低频采样// 根据系统状态动态调整
}
九、常见问题及解决方案
9.1 按键检测不准确
- 问题:ADC值波动导致误检测
- 解决:调整滤波参数,增大KEY_RANGE
9.2 响应速度慢
- 问题:采样次数过多导致延迟
- 解决:减少SAMPLE_CNT,优化采样算法
9.3 多按键同时按下检测
- 问题:组合按键检测不稳定
- 解决:精确测量组合电阻值,优化判断逻辑
