基于树莓派驱动框架IO口驱动编写

1 驱动代码编写

根据BCM2835芯片手册编写引脚4 IO口驱动,将引脚4设置为输出模式进行使用
在这里插入图片描述

该图有用信息为:
GPFSEL io口功能寄存器,控制io口输出输入模式
GPSET0 输出寄存器偏移量为0x1C,功能为输出1
GPSET1 输出寄存器偏移量为0x20,功能为输出1
GPCLR0 清除寄存器偏移量为0x28,功能为清0
GPCLR1 清除寄存器偏移量为0x2C,功能为清0
在这里插入图片描述

该图有用信息为:FSEL4为引脚4,控制位是12-14位,它属于GPFSEL0寄存器,将12-14位设置为001就是output模式
请添加图片描述
该图有用信息:
GPSETn: GPIO引脚输出寄存器,控制引脚输出1的寄存器
设置n号引脚输出1,就将n位置1。无效则置0
GPSET0寄存器属于0-31位,控制引脚0-31
GPSET1寄存器数去32-53位,控制引脚32-54
在这里插入图片描述
该图有用信息:
GPCLRn:GPIO引脚输出清除寄存器,控制引脚输出0的寄存器
设置n号引脚输出0,就将n位置1。无效则置0
GPCLR0寄存器属于0-31位,控制引脚0-31
GPCLR1寄存器数去32-53位,控制引脚32-54

注意:
树莓派3B的CPU为BCM2835,IO空间的起始地址0x3f000000加上GPIO的偏移量0x2000000,所以GPIO的物理地址应该是从0x3f200000开始的,然后在这个基础上进行Linux系统的MMU内存虚拟化管理,映射到虚拟地址上。BCM2835芯片手册中的地址是总线地址,0x3f000000才是起始地址,这是造成大部分人驱动出现“段错误”的原因。

信息整理:
将引脚4设置为output模式使用FSEL4寄存器,输出1使用GPSET0寄存器,输出0使用GPCLR0寄存器
GPFSEL0         12-14位设置为001         *GPFSEL4 |= ~(0x6 << 12); *GPFSEL4 |= 0x1 << 12          地址:0x3f200000
GPSET0           输出1          *GPSET0 |= 0x1 << 4          地址:0x3f20001C
GPCLR0           输出0          *GPSET0 |= 0x1 << 4          地址:0x3f200028

映射寄存器地址
定义全局变量时不能赋值为变量值,(地址不能赋值给定义时的全局变量)

//映射
*GPFSEL4 = (volatile unsigned int*)ioremap(0x3f200000, 4);
*GPSET0  = (volatile unsigned int*)ioremap(0x3f20001C, 4);
*GPCLR0  = (volatile unsigned int*)ioremap(0x3f200020, 4);
//解映
iounmap(GPCLR0);
iounmap(GPSET0);
iounmap(GPFSEL0);

volatile:
保证地址不会因编译器的优化而被更改,每次直接读取值

ioremap函数:
将物理地址映射成虚拟地址来访问寄存器

void *__ioremap(unsigned long phys_addr, unsigned long size, unsigned long flags)
void *ioremap(unsigned long phys_addr, unsigned long size)

入口: phys_addr:要映射的起始的IO地址;
size:要映射的空间的大小;
flags:要映射的IO空间的和权限有关的标志;
phys_addr:是要映射的物理地址
size:是要映射的长度,单位是字节
头文件:io.h

iounmap函数:

解除映射:void iounmap(void* addr)//取消ioremap所映射的IO地址

copy_to_user函数:

static inline unsigned long __must_check copy_to_user(void __user *to, const void *from, unsigned long n)

to 目标地址,这个地址是用户空间的地址

from 源地址,这个地址是内核空间的地址

n 将要拷贝的数据的字节数

copy_form_user函数:

static inline unsigned long __must_check copy_from_user(void *to, const void __user *from, unsigned long n)

to 目标地址,这个地址是内核空间的地址

from 源地址,这个地址是用户的地址

n 将要拷贝的数据的字节数

先初始化驱动设备,再映射寄存器地址,结束先取消映射寄存器地址,再卸载驱动设备

驱动代码pin4_drive.c

#include <linux/fs.h>            //file_operations声明
#include <linux/module.h>    //module_init  module_exit声明
#include <linux/init.h>      //__init  __exit 宏定义声明
#include <linux/device.h>        //class  devise声明
#include <linux/uaccess.h>   //copy_from_user 的头文件
#include <linux/types.h>     //设备号  dev_t 类型声明
#include <asm/io.h>          //ioremap iounmap的头文件

static struct class *pin4_class;
static struct device *pin4_class_dev;

static dev_t devno;                //设备号
static int major = 230;                    //主设备号
static int minor = 0;                      //次设备号
static char *module_name = "pin4";   //模块名

volatile unsigned int *GPFSEL0 = NULL;
volatile unsigned int *GPSET0  = NULL;
volatile unsigned int *GPCLR0  = NULL;

//led_open函数
static int pin4_open(struct inode *inode, struct file *file)
{
    printk("pin4_open\n");
    *GPFSEL0 &= ~(0x6 << 12);   //set pin 4 output 
    *GPFSEL0 |= (0x1 << 12);

    return 0;
}

//led_write函数
static ssize_t pin4_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
        char cmd;

        copy_from_user(&cmd, buf, count);

        printk("cmd %c\n", cmd);
        if (cmd == '1') {
                printk("set 1\n");
                *GPSET0 |= (0x1 << 4);
        } else if (cmd == '0') {
                printk("set 0\n");
                *GPCLR0 |= (0x1 << 4);
        } else {
                printk("input false!\n");
        }

        return 0;
}

static struct file_operations pin4_fops = {
    .owner = THIS_MODULE,
    .open  = pin4_open,
    .write = pin4_write,
};

int __init pin4_drv_init(void)   //真实驱动入口
{
    devno = MKDEV(major,minor);  //创建设备号
    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(0x3f200000, 4);
    GPSET0  = (volatile unsigned int*)ioremap(0x3f20001C, 4);
    GPCLR0  = (volatile unsigned int*)ioremap(0x3f200028, 4);
    printk("pin4_init\n");

    return 0;
}

void __exit pin4_drv_exit(void)
{
    iounmap(GPCLR0);
    iounmap(GPSET0);
    iounmap(GPFSEL0);

    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");

测试文件text.c

#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>

int main(){
  		int ret;
  
        int fd = open("/dev/pin4",O_RDWR);
        if(fd < 0)
        {
                printf("open file fail\n");
                exit(-1);
        }
        char cmd;
        printf("高电平输入1,低电平输入0\n");
        scanf("%c", &cmd);
        ret = write(fd, &cmd, 1);
        if (ret < 0)
        {
                printf("write fail\n");
        }

        return 0;
}

2 驱动代码编译运行

编译运行过程参考博客:
https://blog.csdn.net/weixin_54178481/article/details/119037397

运行结果
请添加图片描述

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值