美好的一天从点灯开始
学好Linux驱动从点灯大师做起
本人也是接触Linux不久,可能有些问题也没考虑到,以下仅是个人观点,欢迎留言,共同进步,话不多说,直接步入正题。
一、实验说明本次实验采用设备树编程
开发板基于树莓派4B
linux内核版本:linux-rpi-5.15.y
开发平台:ubuntu交叉编译
LED为高电平点亮
为了方便,我是直接在根节点下添加一个ledtest节点,要不然放得太深不好找
首先在linux内核中找到对应的设备树文件
这里我是修改了bcm2711-rpi-4.dts文件
linux内核设备树文件路径在arch/arm/boot/dts目录下,可以在该目录下找到该文件
设备树的语法我这里不细讲,设备树的语法可以自己去搜,网上一搜一大把
在根节点下添加以下节点
ledtest{
#address-cells = <1>;
#size-cells = <1>;
compatible = "ledtest";
pinctrl-names = "default";
gpios = <&gpio 26 GPIO_ACTIVE_HIGH>;
status = "okay";
};
属性#address-cells 和#size-cells 都为 1,表示 reg 属性中起始地址占用一个字长(cell),地址长度也占用一个字长(cell)
属性 compatbile 设置 ledtest节点兼容性为“ledtest”
pinctrl-names 属性,此属性描述 pinctrl 名字为“default”。
gpios 属性值,表示此 LED 所使用的 GPIO 引脚的信息
属性 status 设置状态为“okay”。
本次我们使用的引脚编码是: BCM 26
可以使用gpio readall 命令查看树莓派引脚分布和状态,设置自己想要设置的引脚,不一定要是26
保存然后编译设备树文件,使用命令make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- dtbs
然后把编译好的.dtb文件放到板子里面
然后就可以写驱动程序了,我们可以使用of函数来读取设备树里面的内容,以下是我用到的of函数
of_find_node_by_path 用来打开设备节点
of_get_named_gpio 用来获取设备树中的某个属性
然后通过读取设备树属性获取到引脚编码,再通过gpio_direction_output函数配置引脚为输出,gpio_set_value函数来设置引脚输出的电平,后面加上注册设备号,注册字符设备,自动创建设备节点,这样编程的思路就大致理好了。
我这里是把控制LED的程序写在了gpioled_write函数里面,目的是能让应用层来控制LED的亮灭,为了方便我这里把printk的打印级别设置为最高了,即KERN_EMERG。copy_from_user函数的作用从用户空间拷贝数据到内核空间,话不多说,直接上代码。
gpioled.c
#include四、编写MakeFile程序#include #include #include #include #include #include #include #include #include #include #include #include #include #include #define GPIOLED_NAME "gpioled" #define GPIOLED_COUNT 1 #define LEDOFF 0 #define LEDON 1 struct gpioled_dev { struct cdev cdev; dev_t devid; struct class *class; struct device *device; int major; int minor; struct device_node *nd; int led_gpio; }; static struct gpioled_dev gpioled; static int gpioled_open(struct inode *inode, struct file *filp) { printk(KERN_EMERG "gpioled_openn"); filp->private_data = &gpioled; return 0; } static int gpioled_release(struct inode *inode, struct file *filp) { printk(KERN_EMERG "gpioled_releasen"); return 0; } static ssize_t gpioled_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos) { int retvalue; unsigned char databuf[1]; unsigned char ledstat; struct gpioled_dev *dev = filp->private_data; printk(KERN_EMERG "gpioled_writen"); retvalue = copy_from_user(databuf, buf, count); if(retvalue < 0) { printk(KERN_EMERG "kernel write failed!rn"); return -EFAULT; } ledstat = databuf[0]; if(ledstat == LEDON) { printk(KERN_EMERG "打开led灯n"); gpio_set_value(dev->led_gpio, 1); } else if(ledstat == LEDOFF) { printk(KERN_EMERG "关闭led灯n"); gpio_set_value(dev->led_gpio, 0); } return 0; } static const struct file_operations gpioled_fops = { .owner = THIS_MODULE, .open = gpioled_open, .release = gpioled_release, .write = gpioled_write, }; static int __init gpioled_init(void) { int ret; printk(KERN_EMERG "gpioled_initn"); gpioled.major = 0; gpioled.nd = of_find_node_by_path("/ledtest"); if(gpioled.nd == NULL) { printk(KERN_EMERG "ledtest node not find!rn"); return -EINVAL; } gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "gpios", 0); if(gpioled.led_gpio < 0) { printk(KERN_EMERG "can't get gpios"); return -EINVAL; } printk(KERN_EMERG "gpios num = %dn", gpioled.led_gpio); ret = gpio_direction_output(gpioled.led_gpio, 1); if(ret < 0) { printk(KERN_EMERG "can't set gpio!n"); } if(gpioled.major) { gpioled.devid = MKDEV(gpioled.major, 0); ret = register_chrdev_region(gpioled.devid, GPIOLED_COUNT, GPIOLED_NAME); } else { ret = alloc_chrdev_region(&gpioled.devid, 0, GPIOLED_COUNT, GPIOLED_NAME); gpioled.major = MAJOR(gpioled.devid); gpioled.minor = MINOR(gpioled.devid); } if(ret < 0) { printk(KERN_EMERG "gpioled chrdev_region err!n"); goto fail_devid; } printk(KERN_EMERG "gpioled major:%d, minor:%dn", gpioled.major, gpioled.minor); gpioled.cdev.owner = THIS_MODULE; cdev_init(&gpioled.cdev, &gpioled_fops); ret = cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_COUNT); if(ret < 0) { goto fail_cdev; } gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME); if(IS_ERR(gpioled.class)) { ret = PTR_ERR(gpioled.class); goto fail_class; } gpioled.device = device_create(gpioled.class, NULL, gpioled.devid, NULL, GPIOLED_NAME); if(IS_ERR(gpioled.device)) { ret = PTR_ERR(gpioled.device); goto fail_device; } return 0; fail_device: class_destroy(gpioled.class); fail_class: cdev_del(&gpioled.cdev); fail_cdev: unregister_chrdev_region(gpioled.devid, GPIOLED_COUNT); fail_devid: return ret; } static void __exit gpioled_exit(void) { printk(KERN_EMERG "gpioled_exitn"); cdev_del(&gpioled.cdev); unregister_chrdev_region(gpioled.devid, GPIOLED_COUNT); device_destroy(gpioled.class, gpioled.devid); class_destroy(gpioled.class); } module_init(gpioled_init); module_exit(gpioled_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("xxx");
MakeFile
KERNELDIR := /home/pi/linux/pi4/linux-rpi-5.15.y CURRENT_PATH := $(shell pwd) obj-m := gpioled.o build: kernel_modules kernel_modules: $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- clean: $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean五、编写APP程序
gpioledAPP.c
#include六、编译测试#include #include #include #include #include int main(int argc, char *argv[]) { char *pathname; char data; int fd; pathname = argv[1]; if(argc != 3) { printf("%s 输入错误!n", pathname); return -1; } if(argv[2][1] != ' ') { printf("%s 输入错误!n", pathname); return -1; } fd = open(pathname, O_RDWR); if(fd < 0) { printf("open %s error!n", pathname); } data = (char)atoi(argv[2]); if(write(fd, &data, sizeof(data)) < 0) { printf("write %s error!n", pathname); } if(close(fd) == -1) { printf("close %s error!n", pathname); } return 0; }
在工作目录下
命令行输入make编译驱动程序
命令行输入arm-linux-gnueabihf-gcc gpioledAPP.c -o gpioledAPP 编译APP应用程序
传到板子上去,使用insmod命令加载驱动模块
sudo ./gpioledAPP /dev/gpioled 1 能实现亮灯
sudo ./gpioledAPP /dev/gpioled 0 能实现灭灯
好的,实验完成



