Home » 朱老师嵌入式 » 串口输出和按键消抖

串口输出和按键消抖

编 辑:Y ┊ 时 间:2022年04月22日 ┊ 访问: 16 次

串口输出和按键消抖

基于串口标准输出的按键调试

(1)以之前的串口stdio的工程为基础来移植添加轮询方式按键处理。
(2)注意USB下载方式可能有错误(有可能不下载,也有可能下载了执行不对),解决方案是用SD卡启动来替代。

什么是按键消抖

(1)按键这种物理器件本身会有抖动信号,抖动信号指的是在电平由高到低(也就是按键按下时)或者电平由低到高(也就是按键弹起时)过程中,电平的变化不是立刻变化,而是经过了一段时间的不稳定期才完成变化,在这个不稳定期间电平可能会时高时低反复变化,这个不稳定期就叫抖动(抖动期内获取按键信息是不可靠的,要想办法消抖)。
(2)什么叫消抖?消抖就是用硬件或者软件方法来尽量减小抖动期对按键获取的影响。消抖常用2种思路:第一是硬件消抖,消抖思路就是尽量减小抖动时间,方法是通过硬件添加电容等元件来减小抖动;第二是软件消抖,消抖思路是发现一次按键按下/弹起事件后,不立即处理按键,而是延时一段时间(一般10~20ms,这就是消抖时间)后再次获取按键键值,如果此次获取和上次一样是按下/弹起,那就认为真的按下/弹起了。
(3)一般比较精密需要的时候,需要硬件消抖和软件消抖一起配合。

clock.c

// 时钟控制器基地址
#define ELFIN_CLOCK_POWER_BASE        0xE0100000    

// 时钟相关的寄存器相对时钟控制器基地址的偏移值
#define APLL_LOCK_OFFSET        0x00        
#define MPLL_LOCK_OFFSET        0x08

#define APLL_CON0_OFFSET        0x100
#define APLL_CON1_OFFSET        0x104
#define MPLL_CON_OFFSET            0x108

#define CLK_SRC0_OFFSET            0x200
#define CLK_SRC1_OFFSET            0x204
#define CLK_SRC2_OFFSET            0x208
#define CLK_SRC3_OFFSET            0x20c
#define CLK_SRC4_OFFSET            0x210
#define CLK_SRC5_OFFSET            0x214
#define CLK_SRC6_OFFSET            0x218
#define CLK_SRC_MASK0_OFFSET    0x280
#define CLK_SRC_MASK1_OFFSET    0x284

#define CLK_DIV0_OFFSET            0x300
#define CLK_DIV1_OFFSET            0x304
#define CLK_DIV2_OFFSET            0x308
#define CLK_DIV3_OFFSET            0x30c
#define CLK_DIV4_OFFSET            0x310
#define CLK_DIV5_OFFSET            0x314
#define CLK_DIV6_OFFSET            0x318
#define CLK_DIV7_OFFSET            0x31c

#define CLK_DIV0_MASK            0x7fffffff

// 这些M、P、S的配置值都是查数据手册中典型时钟配置值的推荐配置得来的。
// 这些配置值是三星推荐的,因此工作最稳定。如果是自己随便瞎拼凑出来的那就要
// 经过严格测试,才能保证一定对。
#define APLL_MDIV                   0x7d        // 125
#define APLL_PDIV               0x3
#define APLL_SDIV               0x1

#define MPLL_MDIV                0x29b        // 667
#define MPLL_PDIV                0xc
#define MPLL_SDIV                0x1

#define set_pll(mdiv, pdiv, sdiv)    (1<<31 | mdiv<<16 | pdiv<<8 | sdiv)
#define APLL_VAL            set_pll(APLL_MDIV,APLL_PDIV,APLL_SDIV)
#define MPLL_VAL            set_pll(MPLL_MDIV,MPLL_PDIV,MPLL_SDIV)


#define REG_CLK_SRC0    (ELFIN_CLOCK_POWER_BASE + CLK_SRC0_OFFSET)
#define REG_APLL_LOCK    (ELFIN_CLOCK_POWER_BASE + APLL_LOCK_OFFSET)
#define REG_MPLL_LOCK    (ELFIN_CLOCK_POWER_BASE + MPLL_LOCK_OFFSET)
#define REG_CLK_DIV0    (ELFIN_CLOCK_POWER_BASE + CLK_DIV0_OFFSET)
#define REG_APLL_CON0    (ELFIN_CLOCK_POWER_BASE + APLL_CON0_OFFSET)
#define REG_MPLL_CON    (ELFIN_CLOCK_POWER_BASE + MPLL_CON_OFFSET)

#define rREG_CLK_SRC0    (*(volatile unsigned int *)REG_CLK_SRC0)
#define rREG_APLL_LOCK    (*(volatile unsigned int *)REG_APLL_LOCK)
#define rREG_MPLL_LOCK    (*(volatile unsigned int *)REG_MPLL_LOCK)
#define rREG_CLK_DIV0    (*(volatile unsigned int *)REG_CLK_DIV0)
#define rREG_APLL_CON0    (*(volatile unsigned int *)REG_APLL_CON0)
#define rREG_MPLL_CON    (*(volatile unsigned int *)REG_MPLL_CON)


void clock_init(void)
{
    // 1 设置各种时钟开关,暂时不使用PLL
    rREG_CLK_SRC0 = 0x0;

    // 2 设置锁定时间,使用默认值即可
    // 设置PLL后,时钟从Fin提升到目标频率时,需要一定的时间,即锁定时间
    rREG_APLL_LOCK = 0x0000ffff;
    rREG_MPLL_LOCK = 0x0000ffff;

    // 3 设置分频
    // 清bit[0~31]
    rREG_CLK_DIV0 = 0x14131440;

    // 4 设置PLL
    // FOUT = MDIV*FIN/(PDIV*2^(SDIV-1))=0x7d*24/(0x3*2^(1-1))=1000 MHz
    rREG_APLL_CON0 = APLL_VAL;
    // FOUT = MDIV*FIN/(PDIV*2^SDIV)=0x29b*24/(0xc*2^1)= 667 MHz
    rREG_MPLL_CON = MPLL_VAL;

    // 5 设置各种时钟开关,使用PLL
    rREG_CLK_SRC0 = 0x10001111;
}

key.c

#include "stdio.h"

// 定义操作寄存器的宏
#define GPH0CON        0xE0200C00
#define GPH0DAT        0xE0200C04

#define GPH2CON        0xE0200C40
#define GPH2DAT        0xE0200C44

#define rGPH0CON    (*(volatile unsigned int *)GPH0CON)
#define rGPH0DAT    (*(volatile unsigned int *)GPH0DAT)
#define rGPH2CON    (*(volatile unsigned int *)GPH2CON)
#define rGPH2DAT    (*(volatile unsigned int *)GPH2DAT)

// 初始化按键
void key_init(void)
{
    // 设置GPHxCON寄存器,设置为输入模式
    // GPH0CON的bit8~15全部设置为0,即可
    rGPH0CON &= ~(0xFF<<8);
    // GPH2CON的bit0~15全部设置为0,即可
    rGPH2CON &= ~(0xFFFF<<0);
}

void delay20ms(void)
{
    // 这个函数作用是延时20ms
    // 因为我们这里是裸机程序,且重点不是真的要消抖,而是教学
    // 所以我这里这个程序只是象征性的,并没有实体
    // 如果是研发,那就要花时间真的调试出延时20ms的程序
    int i, j;

    for (i=0; i<100; i++)
    {
        for (j=0; j<1000; j++)
        {
            i * j;
        }
    }
}


void key_polling(void)
{
    // 依次,挨个去读出每个GPIO的值,判断其值为1还是0.如果为1则按键按下,为0则弹起

    // 轮询的意思就是反复循环判断有无按键,隔很短时间
    while (1)
    {
        // 对应开发板上标着LEFT的那个按键
        if (rGPH0DAT & (1<<2))
        {
            // 为1,说明没有按键
            led_off();
        }
        else
        {
            // 添加消抖
            // 第一步,延时
            delay20ms();
            // 第二步,再次检验按键状态
            if (!(rGPH0DAT & (1<<2)))
            {
                // 为0,说明有按键
                led1();
                printf("key left.\n");
            }
        }

        // 对应开发板上标着DOWN的那个按键
        if (rGPH0DAT & (1<<3))
        {
            // 为1,说明没有按键
            led_off();
        }
        else
        {
            // 为0,说明有按键
            led2();
            printf("key down.\n");
        }

        // 对应开发板上标着UP的那个按键
        if (rGPH2DAT & (1<<0))
        {
            // 为1,说明没有按键
            led_off();
        }
        else
        {
            // 为0,说明有按键
            led3();
        }
    }
}

main.c

#include "stdio.h"

void uart_init(void);

int main(void)
{
    uart_init();
    key_init();

    key_polling();

    return 0;
}

uart.c

#define GPA0CON        0xE0200000
#define UCON0         0xE2900004
#define ULCON0         0xE2900000
#define UMCON0         0xE290000C
#define UFCON0         0xE2900008
#define UBRDIV0     0xE2900028
#define UDIVSLOT0    0xE290002C
#define UTRSTAT0    0xE2900010
#define UTXH0        0xE2900020    
#define URXH0        0xE2900024    

#define rGPA0CON    (*(volatile unsigned int *)GPA0CON)
#define rUCON0        (*(volatile unsigned int *)UCON0)
#define rULCON0        (*(volatile unsigned int *)ULCON0)
#define rUMCON0        (*(volatile unsigned int *)UMCON0)
#define rUFCON0        (*(volatile unsigned int *)UFCON0)
#define rUBRDIV0    (*(volatile unsigned int *)UBRDIV0)
#define rUDIVSLOT0    (*(volatile unsigned int *)UDIVSLOT0)
#define rUTRSTAT0        (*(volatile unsigned int *)UTRSTAT0)
#define rUTXH0        (*(volatile unsigned int *)UTXH0)
#define rURXH0        (*(volatile unsigned int *)URXH0)

// 串口初始化程序
void uart_init(void)
{
    // 初始化Tx Rx对应的GPIO引脚
    rGPA0CON &= ~(0xff<<0);            // 把寄存器的bit0~7全部清零
    rGPA0CON |= 0x00000022;            // 0b0010, Rx Tx
    
    // 几个关键寄存器的设置
    rULCON0 = 0x3;
    rUCON0 = 0x5;
    rUMCON0 = 0;
    rUFCON0 = 0;
    
    // 波特率设置    DIV_VAL = (PCLK / (bps x 16))-1
    // PCLK_PSYS用66MHz算        余数0.8
    //rUBRDIV0 = 34;    
    //rUDIVSLOT0 = 0xdfdd;
    
    // PCLK_PSYS用66.7MHz算        余数0.18
    // DIV_VAL = (66700000/(115200*16)-1) = 35.18
    rUBRDIV0 = 35;
    // (rUDIVSLOT中的1的个数)/16=上一步计算的余数=0.18
    // (rUDIVSLOT中的1的个数 = 16*0.18= 2.88 = 3
    rUDIVSLOT0 = 0x0888;        // 3个1,查官方推荐表得到这个数字
}


// 串口发送程序,发送一个字节
void putc(char c)
{                      
    // 串口发送一个字符,其实就是把一个字节丢到发送缓冲区中去
    // 因为串口控制器发送1个字节的速度远远低于CPU的速度,所以CPU发送1个字节前必须
    // 确认串口控制器当前缓冲区是空的(意思就是串口已经发完了上一个字节)
    // 如果缓冲区非空则位为0,此时应该循环,直到位为1
    while (!(rUTRSTAT0 & (1<<1)));
    rUTXH0 = c;
}

// 串口接收程序,轮询方式,接收一个字节
char getc(void)
{
    while (!(rUTRSTAT0 & (1<<0)));
    return (rURXH0 & 0x0f);
}

start.S


#define WTCON        0xE2700000

#define SVC_STACK    0xd0037d80

.global _start                    // 把_start链接属性改为外部,这样其他文件就可以看见_start了
_start:
    // 第1步:关看门狗(向WTCON的bit5写入0即可)
    ldr r0, =WTCON
    ldr r1, =0x0
    str r1, [r0]
    
    // 第2步:初始化时钟
    bl clock_init
    
    // 第3步:设置SVC栈
    ldr sp, =SVC_STACK
    
    // 第4步:开/关icache
    mrc p15,0,r0,c1,c0,0;            // 读出cp15的c1到r0中
    //bic r0, r0, #(1<<12)            // bit12 置0  关icache
    orr r0, r0, #(1<<12)            // bit12 置1  开icache
    mcr p15,0,r0,c1,c0,0;

    bl main
    
    // 从这里之后就可以开始调用C程序了
    //bl led_blink                    // led_blink是C语言实现的一个函数
    
// 汇编最后的这个死循环不能丢
    b .
    



Copyright © 2026 Y 版权所有.网站运行:13年238天21小时23分