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 %d\r\n",Tkey[0].ReadMeasurement,Tkey[1].ReadMeasurement,Tkey[2].ReadMeasurement);
if(Tkey[0].ReadMeasurement > 1700)//測量值,跟實際pcb按鍵電容的大小有關
{
if(Tkey[0].Realse  == 0){
printf("KEY0 Press\r\n");
}
Tkey[0].Realse = 1;//按鍵按下
}else{
if(Tkey[0].Realse  == 1){
printf("KEY0 Realse\r\n");
}
Tkey[0].Realse = 0;//按鍵釋放
}
if(Tkey[1].ReadMeasurement > 2300)//測量值,跟實際pcb按鍵電容的大小有關
{
if(Tkey[1].Realse  == 0){
printf("KEY1 Press\r\n");
}
Tkey[1].Realse = 1;
}else{
if(Tkey[1].Realse  == 1){
printf("KEY1 Realse\r\n");
}
Tkey[1].Realse = 0;
}
if(Tkey[2].ReadMeasurement > 2400)//測量值,跟實際pcb按鍵電容的大小有關
{
if(Tkey[2].Realse  == 0){
printf("KEY2 Press\r\n");
}
Tkey[2].Realse = 1;
}else{
if(Tkey[2].Realse  == 1){
printf("KEY2 Realse\r\n");
}
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]);
}

}


(博文愛轉不轉,但請註明出處嚇)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章