无意中翻出了大学刚毕业时用来来忽悠老板的触摸按键的程序,突然感概白发又多了。做硬件的不容易,做软件的也不容易,做硬件又做软件的更不容易。。。。
回想起来印象也不深刻,感觉纯粹为了好玩,又发现了键盘边有个有三个焊盘的pcb板,心血来潮把就它翻新了一下。
感觉触摸按键比物理按键简单多了,物理按键还要按键(废话),但是触摸按键的可是是一个铜片,铁片,金属片(反正是导体就行了)。如果手头上又没有pcb按钮的,可以自己随便找个废板,在有铜片的地方挖个按钮引条线出来也是可以的,甚至拿一条导线也可以。手按按钮时要在按键上贴个胶纸绝缘,不然,按下的时候电流都被人体吸光了。
要说明一下,程序和硬件都是借鉴STM8,ST有相关例程,是AN几就忘了。st的例程使用的查询电平的方式,而且连充放电的时间也要去等,我用可是高贵的stm32这么宝贵的cpu时间岂能白白浪费掉,所以我就把它改成了中断触发的方式。
好了,先贴个原理图。
原理图就这么的简单。
PA4,是充电引脚。PA5、PA6、PA7是电平的检测引脚。这里用的是三个按键,R12、R13、R14是充电的限流电阻,这三个电阻要在几百K到几M欧之间,视单片机的性能而定吧,电阻越小电流越大充放电时间就越短,有句话说就是可以避免夜长梦多。但是电阻太小了,时间太短单片机就检测不到了。我在这选的是1M欧,充放电时间大概是10us,R15,R16,R17作用不是太大,不能取太大就是了。
所谓的RC法,就是R和C,R就是电阻,C就是电容也就是按键,利用充放电的时间来检测电容的变化。
更多的资料,百度谷歌很多,就不抛砖头了。
我的按键是这样的,注意要贴上胶布绝缘。
好了贴程序
这里是三个按键,硬件资源使用了两个定时器,4个IO口
TIM2用于时间的记录。
TIM3每100us触发一次,Ttimer_cnt 记录的是触发次数
Ttimer_cnt = 1 现在所有IO都为低电平状态配置所有IO为输入高电平触发中断,并拉高充电的IO口
Ttimer_cnt = 4到这时所有的IO已经充电完毕,可以记录充电的时间
Ttimer_cnt = 5现在的所有IO口都处于高电平状态,配置所有IO为输入低电平触发中断,并拉低充电的IO口,放电
Ttimer_cnt = 8到这时,所有的IO都放电完毕,记录放电时间
Ttimer_cnt = 9计算按键的充放电时间值,滤波。Ttimer_cnt = 0;
芯片使用的是stm32F103
//.h文件
typedef struct
{
uint32_t Pin;//引脚
uint32_t staus;
uint32_t even;
uint32_t Realse;
uint32_t check_cnt;
uint32_t MeasRejected;
uint32_t RejectionCounter;
uint32_t Up_Cnt;
uint32_t Down_Cnt;
uint32_t Temp_Cnt;
uint32_t AcqLoopIndex;
uint32_t Shifter;
uint32_t MaxMeasurement;
uint32_t MinMeasurement;
uint32_t Measurement;
uint32_t FinalMeasurementValue;
uint32_t CumulatedMeasurement;
uint32_t ReadMeasurement;//检测结果
uint32_t Eline;
}Tkey_Action;
//.c文件
#define KEYNUMS 3
//IO方向设置
// IO口设置76543210 IO口设置76543210
#define ACQ_IN(x) {GPIOA->CRL&= ~(0xF << (uint32_t)(x));GPIOA->CRL|= (0x4 << (uint32_t)(x));} //配置为输入高阻
#define ACQ_OUT(x) {GPIOA->CRL&= ~(0xF << (uint32_t)(x));GPIOA->CRL|= (0x8 << (uint32_t)(x));} //配置为推挽输出
//IO口操作
#define OUT_OUT PAout(4) //充放电引脚
#define SAMPLING_SHIFTER_LOOP_START (1)
#define SAMPLING_SHIFTER_NB_LOOPS (8)
#define MAX_MEAS_COEFF (0x150) //检测结果误差最大值自己调一个合适的值 > 255
#define MIN_MEAS_COEFF (0xBC)//检测结果误差允许最小值自己调一个合适的值 < 255
#define MAX_REJECTED_MEASUREMENTS (5)
Tkey_Action Tkey[KEYNUMS];
uint32_t Tkey_cnt;
uint32_t Ttimer_cnt;
uint32_t Cnt_Dat;
//extern unsigned int cnt;
void Tkey_Init(void);
void TkeyTestFunc(void){//
Tkey_Init();
while(1){
//delay_ms(250);
//printf("Get KEY0 %d,KEY1 %d,KEY2 %d
",Tkey[0].ReadMeasurement,Tkey[1].ReadMeasurement,Tkey[2].ReadMeasurement);
if(Tkey[0].ReadMeasurement > 1700)//测量值,跟实际pcb按键电容的大小有关
{
if(Tkey[0].Realse == 0){
printf("KEY0 Press
");
}
Tkey[0].Realse = 1;//按键按下
}else{
if(Tkey[0].Realse == 1){
printf("KEY0 Realse
");
}
Tkey[0].Realse = 0;//按键释放
}
if(Tkey[1].ReadMeasurement > 2300)//测量值,跟实际pcb按键电容的大小有关
{
if(Tkey[1].Realse == 0){
printf("KEY1 Press
");
}
Tkey[1].Realse = 1;
}else{
if(Tkey[1].Realse == 1){
printf("KEY1 Realse
");
}
Tkey[1].Realse = 0;
}
if(Tkey[2].ReadMeasurement > 2400)//测量值,跟实际pcb按键电容的大小有关
{
if(Tkey[2].Realse == 0){
printf("KEY2 Press
");
}
Tkey[2].Realse = 1;
}else{
if(Tkey[2].Realse == 1){
printf("KEY2 Realse
");
}
Tkey[2].Realse = 0;
}
}
}
void Tkey_Exti_Init(void)//按键的外部中断检测
{
GPIO_InitTypeDef GPIO_InitStructure;
EXTI_InitTypeDef EXTI_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); //复用时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_Pin_5);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_Pin_6);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_Pin_7);
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; //设置为中断请求
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising; //设置中断触发方式为下降沿触发
EXTI_InitStructure.EXTI_LineCmd = ENABLE; //外部中断使能
EXTI_InitStructure.EXTI_Line = EXTI_Line5; //选择中断线路1
EXTI_Init(&EXTI_InitStructure);
EXTI_InitStructure.EXTI_Line = EXTI_Line6; //选择中断线路1
EXTI_Init(&EXTI_InitStructure);
EXTI_InitStructure.EXTI_Line = EXTI_Line7; //选择中断线路1
EXTI_Init(&EXTI_InitStructure);
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannel = EXTI9_5_IRQn;
NVIC_Init(&NVIC_InitStructure);
}
void Tkey_Timer_Init(void)//相关定时器初始化
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
/* TIM2 and TIM3 clocks enable */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2 | RCC_APB1Periph_TIM3, ENABLE);
/* 1 bit for pre-emption priority, 3 bits for subpriority */
NVIC_SetPriorityGrouping(6);
NVIC_DisableIRQ(TIM2_IRQn);
/* TIM2 configuration -------------------------------------------------------*/
TIM_DeInit(TIM2);
TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
TIM_TimeBaseStructure.TIM_Prescaler = 0x01; /* TIM2CLK = 72 MHz */
TIM_TimeBaseStructure.TIM_Period = 0xFFFF;
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
//TIM_GetCounter(TIM2);
TIM2->ARR = 0xFFFF;
/* Enable the TIM Counter */
TIM2->CR1 |= ((uint16_t)0x0001);
/* Clear the IT pending Bit */
TIM2->SR = ((uint16_t)(~TIM_IT_Update));
/* Enable TIM2 update interrupt */
/* Enable the TIM3 Interrupt */
NVIC_SetPriority(TIM3_IRQn, 0x01); /* 0x00 = 0x01 << 3 | (0x00 & 0x7*/
NVIC_EnableIRQ(TIM3_IRQn);
/* TIM3 configuration -------------------------------------------------------*/
TIM_DeInit(TIM3);
/* TIM3 used for timing, the timing period depends on the sample rate */
TIM_TimeBaseStructure.TIM_Prescaler = 0x00; /* TIM2CLK = 72 MHz */
TIM_TimeBaseStructure.TIM_Period = 7199; //0.10ms
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
//TIM_GetCounter(TIM2);
/* Relaod ARR register */
TIM3->ARR = 7199;
/* Enable the TIM Counter */
TIM3->CR1 |= ((uint16_t)0x0001);
/* Clear the IT pending Bit */
TIM3->SR = ((uint16_t)(~TIM_IT_Update));
TIM3->DIER |= ((1 << 6) | (1 << 0));
}
void Tkey_ParInit(Tkey_Action *Tkey_Std,uint32_t GPIOnums){//按键数据结构初始化
Tkey_Std->Shifter = 1;
Tkey_Std->AcqLoopIndex = 0;
Tkey_Std->MeasRejected = 0;
Tkey_Std->RejectionCounter = 0;
Tkey_Std->staus = 0;
Tkey_Std->Pin = GPIOnums;//引脚
Tkey_Std->Eline = (uint32_t)(1 << GPIOnums);//中断资源
}
void Tkey_Init(void)
{
uint32_t i;
GPIO_InitTypeDef Tkey_Struct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
Tkey_Struct.GPIO_Pin = (GPIO_Pin_4);//充放电IO
Tkey_Struct.GPIO_Speed = GPIO_Speed_50MHz;
Tkey_Struct.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOA,&Tkey_Struct);
GPIO_ResetBits(GPIOA,GPIO_Pin_4);
for(i=0;iIMR &= ~(ExitLine);//失能中断
}
__forceinline void Tkey_Vih(Tkey_Action *Tkey_Std)//等待到达上升沿设置
{
ACQ_IN(Tkey_Std->Pin);//按键检测端口设置为输入高阻态
EXTI->FTSR &= ~(Tkey_Std->Eline);
EXTI->RTSR |= (Tkey_Std->Eline);//设置为上升沿触发
EXTI->IMR |= (Tkey_Std->Eline);//使能中断
}
__forceinline void Tkey_downCheck(void)//记录放电时间
{
uint32_t i;
uint32_t ExitLine = 0;
for(i=0;iIMR &= ~(ExitLine);//失能中断
}
__forceinline void Tkey_Vil(Tkey_Action *Tkey_Std)//等待到达下降沿设置
{
ACQ_IN(Tkey_Std->Pin);//配置为高阻态,等待放电完成
EXTI->FTSR |= (Tkey_Std->Eline);
EXTI->RTSR &= ~(Tkey_Std->Eline);//设置为下降沿触发
EXTI->IMR |= (Tkey_Std->Eline);//使能中断
}
__forceinline void Tkey_Vil_All(void)
{
uint32_t i;
uint32_t ExitLine = 0;
for(i=0;iFTSR |= (ExitLine);
EXTI->RTSR &= ~(ExitLine);//所有检测端口设置为下降沿触发
EXTI->IMR |= (ExitLine);//使能中断
}
__forceinline void Tkey_Vih_All(void)
{
uint32_t i;
uint32_t ExitLine = 0;
for(i=0;iFTSR &= ~(ExitLine);
EXTI->RTSR |= (ExitLine);//所有检测端口设置为上升沿触发
EXTI->IMR |= (ExitLine);//使能中断
}
void Tkey_TDeal(Tkey_Action *Tkey_Std)
{
unsigned int temp;
Tkey_Std->Measurement = (Tkey_Std->Up_Cnt + Tkey_Std->Down_Cnt);//充放电的总时间
Tkey_Std->CumulatedMeasurement += Tkey_Std->Measurement;//统计时间值
if(Tkey_Std->Shifter == 1)//第一次检测,计算出参考值
{
temp = Tkey_Std->Measurement * MAX_MEAS_COEFF;
Tkey_Std->MaxMeasurement = (temp >> 8) + 8;
temp = Tkey_Std->Measurement * MIN_MEAS_COEFF;
Tkey_Std->MinMeasurement = (temp >> 8) + 8;
}
else//非第一次检测判断值是否在范围内
{
if ((Tkey_Std->Measurement < Tkey_Std->MinMeasurement) || (Tkey_Std->Measurement > Tkey_Std->MaxMeasurement))//超出可接收范围
{
Tkey_Std->MeasRejected++;
Tkey_Std->RejectionCounter++;
Tkey_Std->Shifter = 8;//
//break; // Out from 'for SamplingShifter' loop !!!
}
}
Tkey_Std->Shifter ++;//检测次数++
if(Tkey_Std->Shifter > 8)//检测达八次
{
if(Tkey_Std->MeasRejected && (Tkey_Std->RejectionCounter <= MAX_REJECTED_MEASUREMENTS))//非法的次数大于可接受范围
{
Tkey_Std->Shifter = 1;
}
else//重新开始
{
if (Tkey_Std->MeasRejected == 0)//
{
Tkey_Std->FinalMeasurementValue += Tkey_Std->CumulatedMeasurement;
Tkey_Std->Shifter = 1;
Tkey_Std->AcqLoopIndex ++;
}
else // RejectionCounter > MAX_REJECTED_MEASUREMENTS //
{
//break; // Out from 'for AcqLoopIndex' loop !!!
Tkey_Std->AcqLoopIndex = 3;
Tkey_Std->FinalMeasurementValue = 0;
}
}
Tkey_Std->MeasRejected = 0;
Tkey_Std->CumulatedMeasurement = 0;
}
if(Tkey_Std->AcqLoopIndex > 2)//一轮的检测完成
{
if (Tkey_Std->RejectionCounter <= MAX_REJECTED_MEASUREMENTS)
{
Tkey_Std->ReadMeasurement = (Tkey_Std->FinalMeasurementValue >> 3); /* Division by SAMPLING_SHIFTER_NB_LOOPS */
}
else
Tkey_Std->ReadMeasurement = 0;//结果非法
Tkey_Std->AcqLoopIndex = 0;
Tkey_Std->MinMeasurement = 0;
Tkey_Std->MaxMeasurement = 0;
Tkey_Std->RejectionCounter = 0;
Tkey_Std->Shifter = 1;
Tkey_Std->staus = 1;
Tkey_Std->FinalMeasurementValue = 0;
}
}
void TIM2_IRQHandler(void)
{
/* Relaod output compare */
TIM2->ARR = 0xFFFF;
/* Clear TIM2 update interrupt */
TIM2->SR = ((uint16_t)(~TIM_IT_Update));
}
void TIM3_IRQHandler(void)
{
/* Relaod output compare */
TIM3->ARR = 7199;
if(Ttimer_cnt == 1)
{
/*
配置IO输入,上升沿触发,使能中断;
*/
Tkey_Vih_All();
TIM2->CNT = 0;//重置计数器
OUT_OUT = 1;//充电口输出高电平
}else if(Ttimer_cnt == 4)
{
/*
记录下CNT计数值;设置IO口输出模式,使电容屏充满电;
到这个时候,理论上所有的端口都已经充满电并触发中断了
*/
Tkey_upCheck();
}else if(Ttimer_cnt == 5)
{
/*
配置IO输入,下降沿触发,使能中断;
*/
Tkey_Vil_All();
TIM2->CNT = 0;//重置计数器
OUT_OUT = 0;//充电口输出低电平
}else if(Ttimer_cnt == 8)
{
/*
记录下CNT计数值;设置IO口输出模式,使电容按键放光电;
*/
Tkey_downCheck();
}else if(Ttimer_cnt == 9)
{
unsigned int i;
for(i=0;iSR = ((uint16_t)(~TIM_IT_Update));
}
__forceinline void Tkey_RecordTime(Tkey_Action *Tkey_Std){
if(EXTI->PR & (Tkey_Std->Eline))
{
EXTI->IMR &= ~(Tkey_Std->Eline);//失能中断
Tkey_Std->Temp_Cnt = TIM2->CNT;
EXTI->PR |= (Tkey_Std->Eline);
}
}
void EXTI9_5_IRQHandler(void)
{
/*
记录下中断触发时的计数器计数;
*/
uint32_t i;
for(i=0;i
(博文爱转不转,但请注明出处吓)