STM32 PCB触摸按键(RC检测法)

2019-07-14 08:44发布

无意中翻出了大学刚毕业时用来来忽悠老板的触摸按键的程序,突然感概白发又多了。做硬件的不容易,做软件的也不容易,做硬件又做软件的更不容易。。。。 回想起来印象也不深刻,感觉纯粹为了好玩,又发现了键盘边有个有三个焊盘的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

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