我是靠谱客的博主 愤怒小松鼠,最近开发中收集的这篇文章主要介绍STM32 PCB触摸按键(RC检测法),觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

无意中翻出了大学刚毕业时用来来忽悠老板的触摸按键的程序,突然感概白发又多了。做硬件的不容易,做软件的也不容易,做硬件又做软件的更不容易。。。。

回想起来印象也不深刻,感觉纯粹为了好玩,又发现了键盘边有个有三个焊盘的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 %drn",Tkey[0].ReadMeasurement,Tkey[1].ReadMeasurement,Tkey[2].ReadMeasurement);
if(Tkey[0].ReadMeasurement > 1700)//测量值,跟实际pcb按键电容的大小有关
{
if(Tkey[0].Realse  == 0){
printf("KEY0 Pressrn");
}
Tkey[0].Realse = 1;//按键按下
}else{
if(Tkey[0].Realse  == 1){
printf("KEY0 Realsern");
}
Tkey[0].Realse = 0;//按键释放
}
if(Tkey[1].ReadMeasurement > 2300)//测量值,跟实际pcb按键电容的大小有关
{
if(Tkey[1].Realse  == 0){
printf("KEY1 Pressrn");
}
Tkey[1].Realse = 1;
}else{
if(Tkey[1].Realse  == 1){
printf("KEY1 Realsern");
}
Tkey[1].Realse = 0;
}
if(Tkey[2].ReadMeasurement > 2400)//测量值,跟实际pcb按键电容的大小有关
{
if(Tkey[2].Realse  == 0){
printf("KEY2 Pressrn");
}
Tkey[2].Realse = 1;
}else{
if(Tkey[2].Realse  == 1){
printf("KEY2 Realsern");
}
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;i<KEYNUMS;i++){
        Tkey_ParInit(&Tkey[i],i+5);//初始化按键数据参数,按键端口为GPIO5/6/7
    }
    Tkey_Exti_Init();//设置硬件参数
    Tkey_Timer_Init();//启动动定时器
Tkey_cnt = 0;

}

__forceinline void Tkey_upCheck(void)
{
    uint32_t i;
    uint32_t ExitLine = 0;
    
    for(i=0;i<KEYNUMS;i++){
        Tkey[i].Up_Cnt = Tkey[i].Temp_Cnt;记录时间
        PAout(Tkey[i].Pin) = 1;//输出高电平,开始充电
        ACQ_OUT(Tkey[i].Pin);//设置为输出状态
        ExitLine |= Tkey[i].Eline;
    }
    EXTI->IMR &= ~(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;i<KEYNUMS;i++){
        Tkey[i].Up_Cnt = Tkey[i].Temp_Cnt;记录时间
        PAout(Tkey[i].Pin) = 0;//输出低电平,放电
        ACQ_OUT(Tkey[i].Pin);//输出低电平,放电
        ExitLine |= Tkey[i].Eline;
    }   
    EXTI->IMR &= ~(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;i<KEYNUMS;i++){
        ExitLine |= Tkey[i].Eline;
    }
    for(i=0;i<KEYNUMS;i++){
        ACQ_IN(Tkey[i].Pin);//所有检测端口配置为输入
    }
EXTI->FTSR |= (ExitLine);
EXTI->RTSR &= ~(ExitLine);//所有检测端口设置为下降沿触发
EXTI->IMR |= (ExitLine);//使能中断
    
}
__forceinline void Tkey_Vih_All(void)
{
uint32_t i;
    uint32_t ExitLine = 0;
    for(i=0;i<KEYNUMS;i++){
        ExitLine |= Tkey[i].Eline;
    }
    for(i=0;i<KEYNUMS;i++){
        ACQ_IN(Tkey[i].Pin);//所有检测端口配置为输入
    }
    
    EXTI->FTSR &= ~(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;i<KEYNUMS;i++){//统计结果
            Tkey_TDeal(&Tkey[i]);
        }
       Ttimer_cnt = 0;//一个周期的检测结束
    }
Ttimer_cnt ++;
  /* Clear TIM2 update interrupt */
    TIM3->SR = ((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<KEYNUMS;i++){
Tkey_RecordTime(&Tkey[i]);
}

}


(博文爱转不转,但请注明出处吓)

最后

以上就是愤怒小松鼠为你收集整理的STM32 PCB触摸按键(RC检测法)的全部内容,希望文章能够帮你解决STM32 PCB触摸按键(RC检测法)所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(69)

评论列表共有 0 条评论

立即
投稿
返回
顶部