【STM32系列文章】
STM32-01-认识单片机
STM32-02-基础知识
STM32-03-HAL库
STM32-04-时钟树
STM32-05-SYSTEM文件夹
STM32-06-GPIO
STM32-07-外部中断
STM32-08-串口
STM32-09-IWDG和WWDG
STM32-10-定时器
STM32-11-电容触摸按键
STM32-12-OLED模块
STM32-13-MPU
STM32-14-FSMC_LCD
STM32-15-DMA
STM32-16-ADC
STM32-17-DAC
STM32电容触摸按键
-
电容触摸按键原理:
无手指触摸:上电时,电阻作用下,电容
Cs
进行充电,直到电容充满,这时候会有一个充电时间Tcs
.有手指触摸:上电时,电阻作用下,电容
Cs
和Cx
进行充电,电容充满时间会变长,得到充电时间Tcx
.
-
检测电容触摸按键过程:
- TPAD引脚设置为推挽输出,输出低电平,实现电容放电到地
- TPAD引脚设置为浮空输入(IO复位后的状态),电容开始充电
- 同时开启TPAD引脚的输入捕获功能,开始捕获高电平
- 等待充电过程中,上升沿触发(充电到Vth(上升沿的电压值))
- 计算充电时间(定时器捕获/比较寄存器获取)
-
硬件结构图:
-
代码实现:
-
TPAD初始化函数
uint8_t tpad_init(uint16_t psc) { uint16_t buf[10]; uint16_t temp; uint16_t i,j; tpad_timx_cap_init(0XFFFF, psc - 1); //连续读取10次 for(i = 0; i < 10; i++) { buf[i] = tpad_get_val(); delay_init(10); } for(i = 0; i < 9; i++) { for(j = i + 1; j < 10; j++) { if(buf[i] > buf[j]) { temp = buf[i]; buf[i] = buf[j]; buf[j] = temp; } } } temp = 0; for(i = 2; i < 8; i++) { temp += buf[i]; } g_tpad_default_val = temp / 6; printf("g_tpad_default_val:%d\r\n", g_tpad_default_val); if (g_tpad_default_val > 0XFFFF / 2) { return 1; /* 初始化遇到超过TPAD_ARR_MAX_VAL/2的数值,不正常! */ } return 0; }
-
触摸按键输入捕获设置
void tpad_timx_cap_init(uint16_t arr, uint16_t psc) { GPIO_InitTypeDef gpio_init_struct; TIM_IC_InitTypeDef timx_ic_cap_chy; __HAL_RCC_GPIOA_CLK_ENABLE(); //初始化GPIOA时钟 __HAL_RCC_TIM5_CLK_ENABLE(); //初始化TIM5时钟 gpio_init_struct.Pin = GPIO_PIN_1; //PA1 gpio_init_struct.Mode = GPIO_MODE_INPUT; //输入 gpio_init_struct.Pull = GPIO_PULLDOWN; //下拉 gpio_init_struct.Speed = GPIO_SPEED_FREQ_MEDIUM; //中速 HAL_GPIO_Init(GPIOA, &gpio_init_struct); //初始化 g_timx_cap_chy_handle.Instance = TIM5; //定时器基地址 g_timx_cap_chy_handle.Init.Prescaler = psc; //分频系数 g_timx_cap_chy_handle.Init.CounterMode = TIM_COUNTERMODE_UP; //向上计数 g_timx_cap_chy_handle.Init.Period = arr; //自动重装载值 g_timx_cap_chy_handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; //时钟分频因子 HAL_TIM_IC_Init(&g_timx_cap_chy_handle); timx_ic_cap_chy.ICPolarity = TIM_ICPOLARITY_RISING; //上升沿捕获 timx_ic_cap_chy.ICSelection = TIM_ICSELECTION_DIRECTTI; //映射到TI1 timx_ic_cap_chy.ICPrescaler = TIM_ICPSC_DIV1; //输入分频设置为不分频 timx_ic_cap_chy.ICFilter = 0; //输入滤波设置为不滤波 HAL_TIM_IC_ConfigChannel(&g_timx_cap_chy_handle, &timx_ic_cap_chy, TIM_CHANNEL_2); HAL_TIM_IC_Start(&g_timx_cap_chy_handle, TIM_CHANNEL_2); //使能输入捕获和定时器 }
输入捕获映射到
TI1
通道,意味着PA1
引脚的信号将被定时器的第一个输入捕获通道TI1
处理。代码中指定了TIM_ICSELECTION_DIRECTTI
,表示直接选择输入引脚作为捕获源,而不是通过其他中间信号。PA1
引脚的信号映射到定时器通道1(TI1
)的过程是通过硬件内部的多路复用器(multiplexer,简称MUX
)实现的。 -
获取捕获值
uint16_t tpad_get_val(void) { tpad_reset(); //等待捕获上升沿,捕获结束后标志位会置1 while(__HAL_TIM_GET_FLAG(&g_timx_cap_chy_handle, TIM_CHANNEL_2) == 0) { if(g_timx_cap_chy_handle.Instance->CNT > 0xFFFF - 500) { return g_timx_cap_chy_handle.Instance->CNT; } } return TIM5->CCR2; }
-
复位TPAD
void tpad_reset(void) { GPIO_InitTypeDef gpio_init_struct; gpio_init_struct.Pin = GPIO_PIN_1; gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP; gpio_init_struct.Pull = GPIO_PULLUP; gpio_init_struct.Speed = GPIO_SPEED_FREQ_MEDIUM; HAL_GPIO_Init(GPIOA, &gpio_init_struct); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET); //TPAD引脚输出0,放电 delay_ms(5); g_timx_cap_chy_handle.Instance->SR = 0; //清除标记 g_timx_cap_chy_handle.Instance->CNT = 0; //归零 gpio_init_struct.Pin = GPIO_PIN_1; gpio_init_struct.Mode = GPIO_MODE_INPUT; gpio_init_struct.Pull = GPIO_NOPULL; gpio_init_struct.Speed = GPIO_SPEED_FREQ_MEDIUM; HAL_GPIO_Init(GPIOA, &gpio_init_struct); }
- 配置PA1为推挽输出模式,并且设置为上拉电阻。这样可以确保PA1在输出状态下可以稳定的输出高低电平信号。
- 设置PA1引脚为低电平,相当于对TPAD引脚进行放电操作。
- 延时5ms,确保放电操作完成。
- 清除定时器状态寄存器和计数器。
- 配置PA1引脚为输入模式,并且设置为无上下拉电阻,这样,TPAD引脚可以正常接收外部输入信号。
在嵌入式系统中,尤其是在涉及触摸传感器或类似的硬件操作时,先将引脚设置为推挽输出模式再进行放电是一个常见的做法。这种方法可以确保引脚能够快速且有效地放电,从而为后续的操作(例如测量或重新配置引脚为输入模式)提供一个已知的初始状态。
为什么要先设置为推挽输出模式再进行放电?
- 强制性放电:
- 推挽输出模式能够提供较强的驱动能力。通过将引脚设置为推挽输出模式并输出低电平,能够确保引脚上的电容或残留电荷能够迅速放电至0。这对于某些敏感的电路来说是必要的,确保电路在重新配置为输入模式之前没有残余电荷影响测量精度。
- 可靠的初始状态:
- 直接将引脚设置为低电平进行放电在某些情况下可能并不能保证完全的放电,特别是在引脚上有较大的寄生电容时。推挽模式可以提供更可靠的低电平输出,确保引脚电位完全放电至0。
- 硬件保护:
- 通过推挽输出模式放电,可以避免因高阻抗状态导致的浮动电平问题。高阻抗状态下,外界噪声可能会干扰引脚电平,从而影响后续的测量。
-
扫描触摸按键
uint8_t tpad_scan(uint8_t mode) { static uint8_t keyen = 0; /* 0, 可以开始检测; >0, 还不能开始检测; */ uint8_t res = 0; uint8_t sample = 3; /* 默认采样次数为3次 */ uint16_t rval; if (mode) { sample = 6; /* 支持连按的时候,设置采样次数为6次 */ keyen = 0; /* 支持连按, 每次调用该函数都可以检测 */ } rval = tpad_get_maxval(sample); if (rval > (g_tpad_default_val + 100)) /* 大于tpad_default_val+TPAD_GATE_VAL,有效 */ { if (keyen == 0) { res = 1; /* keyen==0, 有效 */ } //printf("r:%d\r\n", rval); /* 输出计数值, 调试的时候才用到 */ keyen = 3; /* 至少要再过3次之后才能按键有效 */ } if (keyen) keyen--; return res; }
-
读取的数据取最大值
uint16_t tpad_get_maxval(uint8_t n) { uint16_t temp = 0; uint16_t maxval = 0; while (n--) { temp = tpad_get_val(); /* 得到一次值 */ if (temp > maxval) maxval = temp; } return maxval; }
-
主函数
int main(void) { uint8_t t = 0; HAL_Init(); /* 初始化HAL库 */ sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */ delay_init(72); /* 延时初始化 */ usart_init(115200); /* 串口初始化为115200 */ led_init(); /* 初始化LED */ tpad_init(6); while (1) { if (tpad_scan(0)) /* 成功捕获到了一次上升沿(此函数执行时间至少15ms) */ { LED1_TOGGLE(); /* LED1翻转 */ } t++; if (t == 10) { t = 0; LED0_TOGGLE(); /* LED0翻转 */ } delay_ms(200); } }
-
-
程序运行流程
声明:资料来源(战舰STM32F103ZET6开发板资源包)
- Cortex-M3权威指南(中文).pdf
- STM32F10xxx参考手册_V10(中文版).pdf
- STM32F103 战舰开发指南V1.3.pdf
- STM32F103ZET6(中文版).pdf
- 战舰V4 硬件参考手册_V1.0.pdf