摩托日记 发表于 2026-5-29 15:43:05

【AI8051U 超简单使用库】2.5:定时器操作,手拨编码器和非阻塞式按键

这个库函数就是把上次我的定时器操作函数进行应用,实现手拨式编码器和按键的读取。
非阻塞式按键是我移植江科大的代码。
现在我把代码发出来希望能帮助到各位
ENCODER.c文件里的


#include "ENCODER.h"
#include "TIM.h"
#include "GPIO.h"
#include "PIN_INT.h"
#include "AI8051U_Delay.h"
#include "type_def.h"//这里将u8用typedef等价为uint8_t

//////////////////////用于定时器中断的/////////////////
// 编码器计数值
volatile int16_t g_encoder_cnt = 0;   
//消抖状态机
static uint8_t enc_last_state= 0;//上次的稳定状态(bit1=B bit0=A)
static uint8_t enc_debounce_cnt= 0;//消抖计数器
//按键键值
uint8_t Key_Num;
//////////////////////用于外部引脚中断的//////////////
uint8_t KeyINT_Num;
static uint8_t key_int_flag=0 ;//=0没有按键中断,=1有按键中断
static uint8_t key_int_pin =0 ;
static uint8_t key_int_port =0 ;//记录触发按键的中断引脚

///用于定时器中断的宏定义////////
// 编码器配置
#define ENC_A_GPIOGPIO0
#define ENC_A_PIN   GPIO_Pin_5
#define ENC_B_GPIOGPIO0
#define ENC_B_PIN   GPIO_Pin_4
//按键配置
#define KEY0_GPIO    GPIO0
#define KEY0_PIN   GPIO_Pin_0
#define KEY1_GPIO    GPIO0
#define KEY1_PIN   GPIO_Pin_1
#define KEY2_GPIO    GPIO0
#define KEY2_PIN   GPIO_Pin_2
#define KEY3_GPIO    GPIO0
#define KEY3_PIN   GPIO_Pin_3

///用于外部引脚中断的宏定义////////
//编码器配置
#define ENC_INT_A_GPIO   GPIO0
#define ENC_INT_A_PIN    GPIO_Pin_5
#define ENC_INT_B_GPIO   GPIO0
#define ENC_INT_B_PIN    GPIO_Pin_4
//按键配置
#define KEY0_INT_GPIO    GPIO0
#define KEY0_INT_PIN   GPIO_Pin_0
#define KEY1_INT_GPIO    GPIO0
#define KEY1_INT_PIN   GPIO_Pin_1
#define KEY2_INT_GPIO    GPIO0
#define KEY2_INT_PIN   GPIO_Pin_2
#define KEY3_INT_GPIO    GPIO0
#define KEY3_INT_PIN   GPIO_Pin_3
/*
原理
正转时候
A: 0 1 1 0   所以同一时间BA = 00(0) 01(1) 11(3) 10(2)
B: 0 0 1 1
翻转时候   所以同一时间BA = 10(2) 11(3) 01(1) 00(0)
A: 0 1 1 0
B: 1 1 0 0

消抖之后确定了一个新状态。并且不同于上次(消抖后的状态)
那么就得判断是正转还是反转。
判断方法就是将:(旧状态<<2) | 新状态

正转:合并后的值
0->1: 00 01 :0x01
1->3: 01 11 :0x07
3->2: 11 10 :0x0E
2->0: 10 00 :0x08

反转:合并后的值
2->3: 10 11 :0x0B
3->1: 11 01 :0x0D
1->0: 01 00 :0x04
0->2: 00 10 :0x02
*/

/*
读取编码器引脚状态
*/
static uint8_t read_encoder_pins(void)
{//把BA对应引脚的状态组合起来
    uint8_t state = 0;
        if(GPIO_ReadInputDataBit(ENC_A_GPIO, ENC_A_PIN)) state |= 0x01;
        if(GPIO_ReadInputDataBit(ENC_B_GPIO, ENC_B_PIN)) state |= 0x02;
    return         state;
}
/*
//这个方案用的是定时器中断里来获取编码器的值,属于是笨办法
//这是对于低速的编码器(手拨的)进行编码值的获取。
//同时这也是非阻塞式按键的初始化流程
timer_id:TIMER_0,TIMER_4, TIMER_2,
            TIMER_1,TIMER_11,TIMER_3,         
times:   进入定时器中断的时间:单位ms
clk_mode :选择定时器的定时时钟源是否进行 12分频
             TIM_CLOCK_1T :直接用系统时钟作为定时器的时钟源
                       TIM_CLOCK_12T:把系统时钟进行12分频之后(除以12)作为时钟源
   priority:定时器中断优先级:对于t0 t1有用
            Priority_0,Priority_1,Priority_2,Priority_3.                       
*/
void Encoder_KEY_Timer_LowSpeed_Init(Timer_ID timer_id, u16 times, u8 clk_mode,u8 priority)
{
/*例:
        //定时器配置函数 Timer_Config是timer的16位自动重装模式通用初始化函数
   Timer_Config(TIMER_0, 2, TIM_CLOCK_1T);//定时2ms
//定时器中断配置函数
   Timer_Int_Config(TIMER_0, 0,ENABLE);//每2ms进入一次
        */               
        //1TH:定时器选择 ,定时时间的配置以及中断使能
//定时器配置函数 Timer_Config是timer的16位自动重装模式通用初始化函数
   Timer_Config(timer_id, times, clk_mode);
//定时器中断配置函数
   Timer_Int_Config(timer_id, priority,ENABLE);
//配置为浮空输入;之前配置为准双向口用示波器测量发现低电平的时候是2.5v       
   GPIO_Init(ENC_A_GPIO, ENC_A_PIN, GPIO_Mode_IN_FLOATING);
   GPIO_Init(ENC_B_GPIO, ENC_B_PIN, GPIO_Mode_IN_FLOATING);
//初始化编码器状态
   enc_last_state = read_encoder_pins();
   enc_debounce_cnt = 0;
       
        //2th:按键的选择
   GPIO_Init(KEY0_GPIO, KEY0_PIN, GPIO_Mode_IPU);//底层是上拉准双向口
   GPIO_Init(KEY1_GPIO, KEY1_PIN, GPIO_Mode_IPU);
   GPIO_Init(KEY2_GPIO, KEY2_PIN, GPIO_Mode_IPU);
   GPIO_Init(KEY3_GPIO, KEY3_PIN, GPIO_Mode_IPU);   
}



//用于中断调用的编码器处理函数。注意配置的是哪个定时器就是调用到哪个定时器的中断服务函数里面
void ENCODER_TIMER_ISR_Handle(void)
{
        //static:修饰局部变量则只在第一次调用的时候初始化,之后的调用还保持上次的值
        //修饰全局变量则限制其作用域为文件内部,其他文件无法访问
        //修饰函数则令作用域仅仅限于定义它的文件
    static uint8_tprev_state =0;
        uint8_t curr_state;
        uint8_t diff ;
        //读取当前引脚状态
        curr_state = read_encoder_pins();

        //如果状态与上次不同:要么是头一次发生了编码器输入。要么是编码器抖动
        //有的编码器在硬件电路设计上有电容滤波所以不用软件消抖:或者把软件消抖的延时改短
        if(curr_state != prev_state)
        {
          enc_debounce_cnt = 0;//消抖计数器清0:以此重新进行消抖计数
                prev_state = curr_state;//现在状态变为过去状态
        }else {//如果现在状态和上次状态一样:要么是没有编码器输入,要么是有编码器输入并且不是抖动
          enc_debounce_cnt++;
                if(enc_debounce_cnt >= 3)//消抖完成
                {
                  if(curr_state != enc_last_state)//如果现态和上次稳定状态不同;说明不是编码器没有输入
                  {
                      diff = (enc_last_state<<2)|curr_state;//根据合并后的值判断方向
                          //正转
                          if((diff==0x01)||(diff==0x07)||(diff==0x0E)||(diff==0x08))g_encoder_cnt++;
                          //反转
                          else if((diff==0x0B)||(diff==0x0D)||(diff==0x04)||(diff==0x02))g_encoder_cnt--;
                      //判断完之后那么现态就是上次稳定状态
                          enc_last_state = curr_state;
                  }//如果状态没有变化说明是编码器没有输入
                }
        }
}

//按键键值获取
uint8_t Key_GetNum(void)
{
    uint8_t temp;
        if(Key_Num)//如果有按键按下
        {
          temp = Key_Num;
                Key_Num=0;
                return temp;
        }return 0;
}

//获取按键状态
static uint8_t Key_GetState(void)
{
    if(GPIO_ReadInputDataBit(KEY0_GPIO, KEY0_PIN)==0)
        {
          return 1;
        }
        if(GPIO_ReadInputDataBit(KEY1_GPIO, KEY1_PIN)==0)
        {
          return 2;
        }
    if(GPIO_ReadInputDataBit(KEY2_GPIO, KEY2_PIN)==0)
        {
          return 3;
        }
        if(GPIO_ReadInputDataBit(KEY3_GPIO, KEY3_PIN)==0)
        {
          return 4;
        }return 0;       
}

//非阻塞式按键处理:用于在定时中断里面进行调用
void KEY_TIMER_ISR_Handle(void)
{
        //static修饰局部变量只在第一次调用所在函数的时候初始化一次
        //之后调用依然保持之前的值
        //修饰函数和全局变量令其只在所在文件内可见
           static uint8_t Count;
        static uint8_t CurrState, PrevState;
       
        Count ++;
        if(Count>=5)//按键消抖
        {
          Count =0;
                PrevState = CurrState;//曾经的现态就是现在的过去态
                CurrState = Key_GetState();//获取真正的现态
                if(CurrState == 0 && PrevState != 0)//现在松手。过去是按下(就是相当于等松手)
                {
                  Key_Num = PrevState;//松手后再把所按按键的键值给全局变量Key_Num
                                          //用于uint8_t Key_GetNum(void)读取   
                }
        }
}

#include "config.h"
#include "ai8051u.h"
#include "OLED.h"
#include "GPIO.h"
#include "tim.h"
#include "encoder.h"
#include "PIN_INT.h"
#include "AI8051U_Delay.h"


void main(void)
{
//        TIM_InitTypeDef      TIM_InitStructure;               //结构定义
    WTST = 0;   //设置程序指令延时参数,赋值为0可将CPU执行指令的速度设置为最快
    EAXSFR();   //扩展SFR(XFR)访问使能
    CKCON = 0;//提高访问XRAM速度
   
        OLED_Init( I2C_P24_P23,23);//p24:scl | p23:sda
    OLED_ShowString(1, 1, "Helloword");

    Encoder_KEY_Timer_LowSpeed_Init(TIMER_0,//选择计时并中断的定时器是0
                                         2,      //每隔2ms进行一次中断
                                        TIM_CLOCK_1T,//时钟源是不进行12分频的主时钟
                                        Priority_0);//中断优先级
    //开启全局中断(51单片机必备,否则所有中断都不生效)
    EA = 1;

    while (1)
    {   //显示编码器的值
      OLED_ShowNum(1, 1, g_encoder_cnt, 4);
                //显示按键的值
      OLED_ShowNum(2, 1, Key_GetNum(), 2);
      delay_ms(100);
        }
}
//========================================================================
// 函数: Timer0_ISR_Handler:这个在#include "AI8051U_Timer_Isr.c"里面被注释掉了
// 描述: Timer0中断函数.
// 参数: none.
// 返回: none.
// 版本: V1.0, 2020-09-23
//========================================================================
void Timer0_ISR_Handler (void) interrupt TMR0_VECTOR                //进中断时已经清除标志
{
        //用于中断调用的编码器处理函数。注意配置的是哪个定时器就是调用到哪个定时器的中断服务函数里面
    ENCODER_TIMER_ISR_Handle();
        //非阻塞式按键处理:用于在定时中断里面进行调用
    KEY_TIMER_ISR_Handle();
}



页: [1]
查看完整版本: 【AI8051U 超简单使用库】2.5:定时器操作,手拨编码器和非阻塞式按键