美好的一天从点灯开始
学好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
#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; /* led所使用的GPIO编号 */
};
static struct gpioled_dev gpioled;
static int gpioled_open(struct inode *inode, struct file *filp)
{
printk(KERN_EMERG "gpioled_open\n");
filp->private_data = &gpioled;
return 0;
}
static int gpioled_release(struct inode *inode, struct file *filp)
{
printk(KERN_EMERG "gpioled_release\n");
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_write\n");
retvalue = copy_from_user(databuf, buf, count);
if(retvalue < 0) {
printk(KERN_EMERG "kernel write failed!\r\n");
return -EFAULT;
}
ledstat = databuf[0]; /* 获取状态值 */
if(ledstat == LEDON)
{
printk(KERN_EMERG "打开led灯\n");
gpio_set_value(dev->led_gpio, 1); /* 打开LED灯 */
}
else if(ledstat == LEDOFF)
{
printk(KERN_EMERG "关闭led灯\n");
gpio_set_value(dev->led_gpio, 0); /* 关闭LED灯 */
}
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_init\n");
gpioled.major = 0;
/* 设置LED所使用的GPIO */
/* 获取设备节点:gpioled */
gpioled.nd = of_find_node_by_path("/ledtest");
if(gpioled.nd == NULL)
{
printk(KERN_EMERG "ledtest node not find!\r\n");
return -EINVAL;
}
/* 获取设备树中的gpio属性,得到LED所使用的LED编号 */
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 = %d\n", 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:%d\n", 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_exit\n");
/* 删除字符设备 */
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
gpioledAPP.c
#include
#include
#include
#include
#include
#include
/** ./gpioledAPP /dev/gpioled <1:2>
* ./gpioledAPP /dev/gpioled 0 灭灯
* ./gpioledAPP /dev/gpioled 1 亮灯
*/
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] != '\0')
{
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 能实现灭灯
好的,实验完成