【嵌入式底层知识修炼】按键和LED共用IO口的分时复用扫描方法


只要我跑的速度够快,寂寞就追不上我
只要Key和Led切换速度够快,人眼就看不出来

————小白


  如果你存在按键Key和显示Led共用同一个IO时,需要分时复用的需求,则可以移植代码进行使用:

  • 代码所占code少于0.5K
  • 线程安全,中断安全
  • 可移植性高,配置容易
  • 注意:系统需要存在一个至少为1ms的时钟中断

01 - PCB原理图例程

  举2个Key和Led共用IO口的例子,当MCU的IO口资源不足时,通常会让一些可以用速度欺骗人眼的做法进行资源的节省,比如Key扫描、Led扫描等,例如Led的扫描只要高于25Hz的速度,人眼就基本看不出闪烁,例如Key扫描只要大于100Hz,人的触感就难以察觉。

例程1

在这里插入图片描述

例程2

在这里插入图片描述

02 - 分时复用的注意项和设计

2.1 - 注意项

  Led和Key的分时复用需要考虑几个情况:
  1、Led扫描的频率,必须高于肉眼能看见闪烁的频率25HZ。一般为了效果较好,都会大于50HZ,也就是Led扫描时间<=20ms
  2、保证分时复用,扫描一定要有先后顺序。要确保Led扫描期间不能进行Key扫描,同时确保Key扫描期间不能进行Led扫描
  3、Led需要备份当前状态。因为扫描Key时Led是不起作用的,Key扫描完成后需要恢复Led的状态
  4、注意Led切换为Key时的准备动作。Led切换为Key时根据原理图要做哪些准备工作以防止切换后Key误触发,而Key切换为Led时按照电路决定是否需要做准备动作
  5、Led扫描时间比Key扫描时间长。考虑到肉眼的观察是最容易受影响的,时刻都能看见,而按键的使用次数则较低

2.2 - 设计

  根据注意项,总体设计如下:
  1、系统需要有一个至少为1ms的时钟中断,里面调用扫描ISR函数LedKeyStatusSwitchService_1ms,用于保证高频扫描
  2、理清状态机,存在2种状态的切换,分别为KeyState和LedState,值得注意的是,Led和Key的数量暂时未知,如果数量较多则实际的扫描动作不宜在中断内进行,此时中断则只做状态切换,所以2个状态分别需要一个记录器
  3、决定取舍,Led为人眼可见,最为敏感,Led的扫描动作直接放在中断进行,舍弃Key的中断扫描,让Key在前台进行扫描

03 - 代码实现

  以例程1的PCB为例,整个分时复用时间为6ms(167Hz),其中Led扫描占据4ms,Key扫描占据2ms,下面为参考代码:

#define LED_SACND_TIME_MS	4
#define KEY_SACND_TIME_MS	2
volatile uint8_t configKeyFlag = 0;
volatile uint8_t configLedFlag = 0;
uint8_t ledKeyStatusSwitchCnt = 0;
 
 /* MCU的底层配置 */
void IOPorts_ConfigKey(void)
{
}
 /* MCU的底层配置 */
void IOPorts_ConfigLed(void)
{
}
 /* 恢复Led的状态 */
void revProcess_AfterConfigLed(void)
{
}
 
 /* 在底层配置为Key之前的预处理,主要是Led电路对Key电路的影响 */
void preProcess_BeforeConfigKey(void)
{
    //COM1 & COM2必须拉高,否则数码管内会输出低电平导致Key误触发
    _IO_setDisplayCOM1(1);
    _IO_setDisplayCOM2(1);
     
    //把复用的IO拉高,防止寄存器内为0导致Key误触发
    _IO_setDisplayS5(1);
    _IO_setDisplayS6(1);
    _IO_setDisplayS7(1);
    _IO_setDisplayS8(1);
}
 
/* 为保证频率,Service一般都在中断内执行 */
void ledKeyStatusSwitchService_1ms(void)
{
    ledKeyStatusSwitchCnt++;
    if(ledKeyStatusSwitchCnt <= LED_SACND_TIME_MS) {
        if ((configLedFlag == 0) && (configKeyFlag == 0)) {
            configLedFlag = 1;
            IOPorts_ConfigLed();
            revProcess_AfterConfigLed();
        }
    } else if(ledKeyStatusSwitchCnt <= (LED_SACND_TIME_MS + KEY_SACND_TIME_MS)) {
        if ((configKeyFlag == 0) && (configLedFlag == 1)) {
            preProcess_BeforeConfigKey();
            IOPorts_ConfigKey();
            configKeyFlag = 1;
            configLedFlag = 0;
             
            //当按键数较少而且扫描时间较短时,可以选择在中断内进行,但一般选择在main中进行
            /*
            keyScanHandler();
            configKeyFlag = 0;
            */
        }
 
    } else if (ledKeyStatusSwitchCnt >= (LED_SACND_TIME_MS + KEY_SACND_TIME_MS + 1)) {
        ledKeyStatusSwitchCnt = 0;
    }
}
 
 /* 在main()前台执行 */
void KeyScandHandler(void)
{
    if (configKeyFlag) {
        keyScan();
        configKeyFlag = 0;
    }
}
  • 14
    点赞
  • 113
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
以下是使用 Arduino 控制单个数码管和 LED 灯的示例程序: ``` int segA = 2; // 数码管 A 段连接的引脚 int segB = 3; // 数码管 B 段连接的引脚 int segC = 4; // 数码管 C 段连接的引脚 int segD = 5; // 数码管 D 段连接的引脚 int segE = 6; // 数码管 E 段连接的引脚 int segF = 7; // 数码管 F 段连接的引脚 int segG = 8; // 数码管 G 段连接的引脚 int ledPin = 9; // LED 灯连接的引脚 void setup() { pinMode(segA, OUTPUT); pinMode(segB, OUTPUT); pinMode(segC, OUTPUT); pinMode(segD, OUTPUT); pinMode(segE, OUTPUT); pinMode(segF, OUTPUT); pinMode(segG, OUTPUT); pinMode(ledPin, OUTPUT); } void loop() { digitalWrite(segA, LOW); // 数码管显示 0 digitalWrite(segB, LOW); digitalWrite(segC, LOW); digitalWrite(segD, LOW); digitalWrite(segE, LOW); digitalWrite(segF, LOW); digitalWrite(segG, HIGH); digitalWrite(ledPin, LOW); // LED 灯灭 delay(1000); digitalWrite(segA, HIGH); // 数码管显示 1 digitalWrite(segB, LOW); digitalWrite(segC, LOW); digitalWrite(segD, HIGH); digitalWrite(segE, HIGH); digitalWrite(segF, HIGH); digitalWrite(segG, HIGH); digitalWrite(ledPin, HIGH); // LED 灯亮 delay(1000); } ``` 在这个示例程序中,我们使用了一个数字输出端来控制 LED 灯的亮灭状态,以及 7 个数字输出端来控制数码管的 7 个段。在 `setup()` 函数中,我们将这些端都设置为输出模式。在 `loop()` 函数中,我们使用 `digitalWrite()` 函数来控制各个段和 LED 的亮灭状态,并使用 `delay()` 函数来控制每个状态的持续时间。当 LED 灯亮时,数码管显示 1;当 LED 灯灭时,数码管显示 0。
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值