• 4.树莓派IO口驱动


    树莓派4b的基地址为0xfe00 0000,因此基地址加上偏移地址得到物理地址为0xfe20 000
    而树莓派3b的基地址为0x3f00 0000,此处容易混淆

    操作I/O口输入、输出0和输入、输出1,根据芯片手册,需要操作三个寄存器
    功能选择寄存器:GPFSEL0 选择I/O口及I/O口功能
    输出设置寄存器:GPSET0 选择I/O口输出1
    输出清除寄存器:GPCLR0 选择I/O口输出0

    功能选择寄存器(GPFSELn)
    由芯片手册得知,GPFSELn (n= 0时控制引脚09,n=1时控制引脚1019,以此类推)为功能选择寄存器,功能选择寄存器用于定义通用 I/O 引脚的操作,有32位,每3位为1一组进行配置一个引脚,如果想要配置引脚4为输出,则应该让GPFSELn = 为GPFSEL0且配置bit 14-12 为001,其他位保持不变。

    输出设置寄存器(GPSETn)
    由芯片手册得知,GPFSETn (n= 0时控制引脚031,n=1时控制引脚3257)为输出设置寄存器,输出设置寄存器用于定义通用 I/O 引脚的操作,有32位,每1位配置一个引脚,如果想要配置引脚4输出1,则应该让GPFSETn = 为GPFSET0且配置bit 4为1,其他位保持不变。

    输出清除寄存器(GPCLRn)
    由芯片手册得知,GPCLRn (n= 0时控制引脚031,n=1时控制引脚3257)为输出设置寄存器,输出设置寄存器用于定义通用 I/O 引脚的操作,有32位,每1位配置一个引脚,如果想要配置引脚4输出1,则应该让GPCLRn = 为GPCLR0且配置bit 4为1,其他位保持不变。

    volatile关键字
    防止寄存器变量被编译器优化,要求每次直接从寄存器读值

    上层代码

    #include 
    #include 
    #include 
    #include 
    
    int main()
    {
            int fd;
            int cmd;
            int data;
            fd = open("/dev/pin4",O_RDWR);
            if(fd < 0)
            {
                    printf("open failed\n");
    
            }else{
                    printf("open success\n");
            }
            printf("please input cmd:1/0\n1:set pin4 high\n0:set pin4 low\n");
            scanf("%d",&cmd);
            if(cmd == 1)
            {
                    data = 1;
            }
            else
            {
                    data = 0;
            }
            printf("data = %d\n",data);
    
            fd = write(fd,&data,4);
    
            return 0;
    }  
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    完整驱动代码

    #include 		 //file_operations声明
    #include     //module_init  module_exit声明
    #include       //__init  __exit 宏定义声明
    #include 	 //class  devise声明
    #include    //copy_from_user 的头文件
    #include      //设备号  dev_t 类型声明
    #include           //ioremap iounmap的头文件
    
    
    static struct class *pin4_class;
    static struct device *pin4_class_dev;
    
    static dev_t devno;                //设备号
    static int major =231;  		   //主设备号
    static int minor =0;			   //次设备号
    static char *module_name="pin4";   //模块名
    
    volatile unsigned int* GPFSEL0 = NULL;
    volatile unsigned int* GPSET0  = NULL;
    volatile unsigned int* GPCLR0  = NULL;
    
    
    static int pin4_read (struct file *file,char __user *buf,size_t size,loff_t *ppos)
    {
    
    
        printk("pin4_read\n");  //内核的打印函数,和pintf类似
        return 0;
    }
    
    //led_open函数
    static int pin4_open(struct inode *inode,struct file *file)
    {
        printk("pin4_open\n");  //内核的打印函数和printf类似
    
        //配置pin4输出模式
    
        *GPFSEL0 &= ~(0x06 << 12); //按位与    0 & 0= 0 ,0 & 1= 0,1 & 0= 0, 1 & 1= 1
                                    //xxxxxxxxxx14 13 12xxxxxxxxx,001取反110,等于6 ;(12 << 0x06)左移12位
                                    //           0 0  1
                                    //为了不影响其他io口,将其取反再&
                                    //
        *GPFSEL0 |= (0x06 << 12);   //按位或   0 | 0= 0 ,  1 | 0= 1  , 0 | 1= 1  ,  1 | 1= 1
                                    //将某位置1,为了不影响其他位的情况下
        return 0;
    }
    
    //led_write函数
    static ssize_t pin4_write(struct file *file,const char __user *buf,size_t count, loff_t *ppos)
    {
    
        int usercmd;
        printk("pin4_write\n");
        //获取上层write函数的值
        copy_from_user(&usercmd,buf,count);
        //配置pin4输出高低电平
        if(usercmd == 1)
        {
            printk("set 1\n");
            *GPSET0 |= (0x01 << 4);
    
        }
        else if (usercmd == 0)
        {
            printk("set 0\n");/* code */
            *GPCLR0 |= (0x01 << 4);
        }
        else
        {
            printk("no\n");
        }
        return 0;
    }
    
    static struct file_operations pin4_fops = {
    
        .owner = THIS_MODULE,
        .open  = pin4_open,
        .write = pin4_write,
        .read  = pin4_read,
    };
    
    int __init pin4_drv_init(void)  //真实驱动程序入口
    {
    
        int ret;
        devno = MKDEV(major,minor);  //创建设备号
        ret   = register_chrdev(major, module_name,&pin4_fops);  //注册驱动  告诉内核,把这个驱动加入到内核驱动的链表中
    
        pin4_class=class_create(THIS_MODULE,"myfirstdemo"); //让代码在dev自动生成设备
        pin4_class_dev =device_create(pin4_class,NULL,devno,NULL,module_name);  //创建设备文件
    
    
        /*对寄存器进行配置*/
        GPFSEL0 = (volatile unsigned int*)ioremap(0xfe200000,4); //需要用ioremap(物理地址,映射到多少个字节)把实际地址转换成虚拟地址才有用,因为是操作mmu,虚拟内存管理;
        GPSET0  = (volatile unsigned int*)ioremap(0xfe20001c,4);
        GPCLR0  = (volatile unsigned int*)ioremap(0xfe200028,4);
    
    
        return 0;
    }
    
    void __exit pin4_drv_exit(void)
    {
        iounmap(GPFSEL0);   //解除映射关系
        iounmap(GPSET0);
        iounmap(GPCLR0);
        device_destroy(pin4_class,devno);
        class_destroy(pin4_class);
        unregister_chrdev(major, module_name);  //卸载驱动
    
    }
    
    module_init(pin4_drv_init);  //入口,内核加载该驱动的时候,这个宏会被调用
    module_exit(pin4_drv_exit);
    MODULE_LICENSE("GPL v2");
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117

    测试结果:
    打开成功
    在这里插入图片描述
    dmesg查看内核打印信息
    在这里插入图片描述
    查看pin4的状态,可看出已设置成输出模式,低电平状态
    在这里插入图片描述
    查看pin4的状态,可看出已设置成输出模式,高电平状态
    在这里插入图片描述
    驱动代码测试完毕

  • 相关阅读:
    关于windows的文件监控管理系统(Java)
    滑动窗口与前缀和算法总结和例题
    vue-router4 |name的作用|query传参|parmas传参|动态路由参数|命名视图|别名alias|前置路由守卫|路由过渡效果|滚动行为
    【Python机器学习】利用AdaBoost元算法提高分类性能——完整的AdaBoost算法的实现
    【Java盲点攻克】「时间与时区系列」让我们一起完全吃透对于时区和日期相关的功能开发原理
    GD32_定时器输入捕获波形频率
    NewStarCTF 2023 公开赛道 WEEK2|WEB 游戏高手
    IDEA Plugin插件开发相关踩坑
    高质量发展项目——冠心病药物治疗管理标准化培训在京顺利举办
    力扣 -- 1745. 分割回文串 IV
  • 原文地址:https://blog.csdn.net/m0_61511416/article/details/127131146