驱动学习笔记8 定时器去抖动,内核空间地址映射操作寄存器

回顾:
1.linux内核等待队列机制
1.1.回顾进程休眠的方法
    应用:sleep
    驱动:msleep/ssleep/schedule/schedule_timeout
    缺陷:随时随地休眠做不到随时随地被唤醒正常运行
    办法:等待队列
1.2.等待队列特点
    能够让进程在内核空间休眠,唤醒(随时随地)
    只能用于进程,中断不可用
    有中断必然有等待队列,有等待队列不一定有中断
1.3.等待队列编程方法:两种
                方法1:步骤
        定义初始化等待队列头对象(全局)
        定义初始化装载休眠进程的容器对象(局部)
        将当前进程添加到等待队列中
        设置进程休眠的类型:可中断
        让进程进入真正休眠,等待被唤醒,代码停止不前
        进程被唤醒,设置为运行态
        从队列中移除
        判断进程唤醒的原因
        事件触发,硬件准备继续,中断触发主动唤醒休眠的进程
    方法2:步骤
        定义初始化等待队列头对象(全局)
        调用高度封装方法决定进程是否休眠
        事件触发,硬件准备继续,中断触发主动唤醒休眠的进程
        编程框架:
            //休眠代码
            休眠
            置假
        
            //唤醒代码
            置真
            唤醒
-------------------------------------------------------------


2.linux内核按键去抖动


2.1.按键抖动产生的原因


    按键属于机械结构,势必产生抖动,接触不良现场


2.2.按键抖动实际产生的波形


    参见:按键去抖动.png


    去除抖动方案:两种
    抖动产生之后,前后两次中断的时间间隔经验值:5~10ms
    硬件去抖动:硬件上添加滤波电路
                优点:去抖动效果最好
                缺点:费钱
    软件去抖动:
                裸板程序:忙延时去抖动,极大浪费CPU资源
                linux内核:采用软件定时器                           
                优点:节省成本
                缺点:去抖动效果没有硬件好


2.3.掌握linux内核利用软件定时器去抖动


    面试时边说边画图展示去抖动原理

添加定时器,利用定时器和超时处理函数唤醒进程


    参见:按键去抖动.png

--------------------------------------------------------------


3.linux内核地址映射  


3.1.明确:在linux系统中,不管是应用程序还是驱动程序
    都不允许直接访问外设的物理地址,要想访问必须将
    物理地址映射到用户虚拟地址或者内核虚拟地址
    一旦映射完毕,应用或者内核程序访问映射的虚拟地址
    就是在访问实际的物理地址
    
    明确:在linux系统中,4G虚拟地址空间的划分
    用户虚拟地址范围:0x00000000~0xBFFFFFFF
    内核虚拟地址范围:0xC0000000~0xFFFFFFFF
    
    问:如何将物理地址映射到内核虚拟地址?
    答:利用大名鼎鼎的ioremap函数
    
    问:如何将物理地址映射到用户虚拟地址?
    答:利用mmap

    结论:一个物理地址可以有多个虚拟地址
               一个虚拟地址不能有多个物理地址

3.2.ioremap/iounmap函数
    函数原型:
    void *ioremap(unsigned long phy_address, unsigned long len)
    函数功能:将物理地址映射到内核虚拟地址
    phy_address:传递要映射的起始的物理地址
    len:传递要映射的物理地址空间的大小
    返回值:返回映射的起始内核虚拟地址
    
    void iounmap(void *vir_address)
    函数功能:解除物理地址和内核虚拟地址的映射关系
    vir_address:传递映射好的起始内核虚拟地址
    
    参考代码: 
    回顾:ARM裸板开关灯
    寄存器                       物理地址 
    GPIOCALTFN0         0xC001C020
    GPIOCOUTENB       0xC001C004
    GPIOCOUT               0xC001C000
    
    //四选一
    GPIOCALTFN0 &= ~(3 << 24);
    GPIOCALTFN0 |= (1 << 24);
    
    //二选一
    GPIOCOUTENB |= (1 << 12);
    
    //输出1和0
    GPIOCOUT |= (1 << 12);
    GPIOCOUT &= ~(1 << 12);
    
    在linux内核中,开关灯,有两种方案:
    1.采用内核提供的GPIO操作库函数
      优点:代码极其简单
      缺点:很神秘,看不到寄存器的操作细节
            只能做输入或者输出操作
            如果作为UART的TX或者RX
            如果作为I2C的SCL或者SDA等
            GPIO库函数毫无办法,无法使用!
            只能自己软件操作寄存器来实现,必须采用方案2
      代码:
          gpio_direction_output(PAD_GPIO_C + 12, 1);
          gpio_direction_output(PAD_GPIO_C + 12, 0);
          
    2.采用直接操作寄存器
      优点:很直白
      缺点:代码极其繁琐
      代码:
      寄存器               物理地址         内核虚拟地址 
    GPIOCALTFN0            0xC001C020          gpiocaltfn0
    GPIOCOUTENB            0xC001C004          gpiocoutenb
    GPIOCOUTENB            0xC001C000          gpiocout 
    
    //地址映射,两种方案:
    方案1:
        unsigned long *gpiocout, *gpiocoutenb, *gpiocaltfn0;
        gpiocout = ioremap(0xC001C000, 4);
        gpiocoutenb = ioremap(0xC001C004, 4);
        gpiocaltfn0 = ioremap(0xC001C020, 4);
    
    方案2:由于寄存器的物理地址空间都是连续的,所以连续映射:
        void *gpiobase; 使用无类型指针地址换算可读性好
        unsigned long *gpiocout, *gpiocoutenb, *gpiocaltfn0;
        gpiobase = ioremap(0xC001C000, 0x24);
        
        //地址换算,使用无类型指针可直接通过手册的地址换算

        若不使用无类型指针,例如使用unsigned long *gpiobase

        gpiocout = (unsigned long *)(gpiobase + 1);可读性差
        gpiocout = (unsigned long *)(gpiobase + 0x00);
        gpiocoutenb = (unsigned long *)(gpiobase + 0x04);
        gpiocaltfn0 = (unsigned long *)(gpiobase + 0x20);
        
        //四选一
        *gpiocaltfn0 &= ~(3 << 24);
        *gpiocaltfn0 |= (1 << 24);
    
        //二选一
        *gpiocoutenb |= (1 << 12);
    
        //输出1和0
        *gpiocout |= (1 << 12);
        *gpiocout &= ~(1 << 12);

案例:编写LED混杂设备驱动,提供ioctl接口实现开关某个灯
      不允许使用GPIO库函数,通过直接访问寄存器来实现

  1 #include<linux/init.h>
  2 #include<linux/module.h>
  3 #include<linux/fs.h>
  4 #include<linux/miscdevice.h>
  5 #include<linux/io.h> //ioremap/iounamp
  6 #include<linux/uaccess.h>
  7 //定义寄存器对应的内核虚拟地址的指针变量
  8 static void *gpiobase;
  9 static unsigned long *gpiocaltfn0, *gpiocoutenb, *gpiocout;//功能选择,输出使能,输出
 10 
 11 #define LED_ON  0x100001
 12 #define LED_OFF 0x100002
 13 static long led_ioctl(struct file *file, unsigned int cmd, unsigned long buf) {
 14     int kindex;
 15     copy_from_user(&kindex, (int *)buf, sizeof(kindex));
 16     switch(cmd){
 17         case LED_ON:
 18             if(kindex == 1){
 19                 *gpiocout &= ~(1 << 12);
 20             }
 21             break;
 22         case LED_OFF:
 23             if(kindex == 1){
 24                 *gpiocout |= (1 << 12);
 25             }
 26             break;
 27     }
 28     //调试信息
 29     printk("ALT:%#X, OUTENB:%#X, OUT:%#X\n", *gpiocaltfn0, *gpiocoutenb, *gpiocout);
 30     return 0;
 31 }
 32 
 33 static struct file_operations led_fops = {
 34     .unlocked_ioctl = led_ioctl
 35 };
 36 
 37 static struct miscdevice led_misc = {
 38     .name = "myled",
 39     .minor = MISC_DYNAMIC_MINOR,
 40     .fops = &led_fops
 41 };
 42 
 43 static int led_init(void){
 44     misc_register(&led_misc);
 45     //将寄存器的物理地址映射到内核虚拟地址上
 46     gpiobase = ioremap(0xC001C000, 0x24);
 47     //换算三个寄存器的内核虚拟地址
 48     gpiocout = (unsigned long *)(gpiobase + 0x00);
 49     gpiocoutenb = (unsigned long *)(gpiobase + 0x04);
 50     gpiocaltfn0 = (unsigned long *)(gpiobase + 0x20);
 51     //四选一
 52     *gpiocaltfn0 &= ~(3 << 24);
 53     *gpiocaltfn0 |= (1 << 24);
 54     //二选一
 55     *gpiocoutenb |= (1 << 12);
 56     //默认输出1
 57     *gpiocout |= (1 << 12);
 58 
 59     return 0;
 60 }
 61 
 62 static void led_exit(void){
 63     *gpiocout |= (1 << 12);
 64     //解除地址映射
 65     iounmap(gpiobase);
 66 
 67     misc_deregister(&led_misc);
 68 
 69 }
 70 
 71 module_init(led_init);
 72 module_exit(led_exit);
 73 MODULE_LICENSE("GPL");


----------------------------------------------------------------------------------

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值