仙居网站设计,展台设计灵感网站,ml域名注册,带屏蔽的网站做水晶头github主页#xff1a;https://github.com/snqx-lqh 本项目github地址#xff1a;https://github.com/snqx-lqh/RaspberryPiDriver 本项目硬件地址#xff1a;https://oshwhub.com/from_zero/shu-mei-pai-kuo-zhan-ban 欢迎交流 笔记说明
如我在驱动开发总览中说的那样https://github.com/snqx-lqh 本项目github地址https://github.com/snqx-lqh/RaspberryPiDriver 本项目硬件地址https://oshwhub.com/from_zero/shu-mei-pai-kuo-zhan-ban 欢迎交流 笔记说明
如我在驱动开发总览中说的那样一般的驱动开发模式就是有3种。
传统设备驱动模式将所有的资源分配放进一个文件中。
PlatformDevice/PlatformDriver模式资源放进PlatformDevice实现驱动放进PlatformDriver文件。
设备树/PlatformDriver模式资源放进设备树实现驱动放进PlatformDriver文件。设备树一种是提前编译内核的时候就编译好还有一种方式是添加设备树插件。
这一节主要说明传统的模式就是将资源全部放进一个文件中处理。
主要参考的文章
正点原子《I.MX6U 嵌入式 Linux 驱动开发指南 V1.81》
韦东山《01_嵌入式Linux应用开发完全手册V5.1_IMX6ULL_Pro开发板》
本节源码路径02_Firmware/02_led_drv_cdev
开发流程
1、初始化一个设备操作函数结构体 struct file_operations并注册内部函数。该结构体就是这个模块文件读写的相关操作
2、编写入口函数在这个里面注册设备并将前面定义的file_operations结构体传入注册函数。自动分配节点需要创建类后创建设备节点。
3、编写出口函数将设备注销。删除类和设备节点。
4、在入口函数完善IO引脚初始化出口函数完善引脚相关处理。
5、在设备操作函数中完善各个操作函数的实现逻辑。
需要的头文件
先说明这部分代码需要包含的头文件。
#include linux/init.h //用于定义模块初始化和清理相关的宏以及函数原型等内容
#include linux/module.h //包含了众多用于构建内核模块的基本定义、宏和函数原型
#include asm/io.h //用于处理输入 / 输出操作尤其是和硬件底层的内存映射 I/O 相关的操作
#include linux/string.h //提供了一系列字符串处理相关的函数
#include linux/fs.h //定义了文件系统操作的各种结构体、函数原型等内容
#include linux/uaccess.h //用于处理用户空间User Space和内核空间Kernel Space之间的数据拷贝操作
#include linux/cdev.h //定义了字符设备相关的结构体设备操作函数结构体
我们需要定义一个struct file_operations结构体以供我们后面操作这个模块并且需要注册这个结构体中的操作函数。
// 通过文件读取得到当前LED的状态
ssize_t led_drv_read(struct file* filp, char __user* buf, size_t len, loff_t* off)
{return 0;
}// 通过向文件写入LED状态控制LED灯
ssize_t led_drv_write(struct file* filp, const char __user* buf, size_t len, loff_t* off)
{return 0;
}const struct file_operations led_fops {.owner THIS_MODULE,.read led_drv_read,.write led_drv_write,
};编写入口函数
入口函数就是我们加载这个模块的时候会调用的函数这里我们将注册一个字符型设备并且动态的为其分配设备号其实还有静态方法指定设备号分配可以看正点原子教程这里不做说明因为感觉没有动态好用。
static dev_t led_dev_num 0; // 设备编号
static struct cdev led_cdev; // 字符设备结构体
static struct class *class NULL; //类
static struct device *device NULL; //设备static int __init led_drv_cdev_init(void)
{// 将该模块注册为一个字符设备并动态分配设备号if (alloc_chrdev_region(led_dev_num, 0, 1, led_drv)) {printk(KERN_ERRfailed to register kernel module!\n);return -1;}printk(KERN_INFOled_drv device major minor is [%d:%d]\n, MAJOR(led_dev_num), MINOR(led_dev_num));//初始化并添加一个cdevcdev_init(led_cdev, led_fops);cdev_add(led_cdev, led_dev_num, 1);//创建类class class_create(led_drv);if (IS_ERR(class)) {return PTR_ERR(class);}//创建设备device device_create(class, NULL, led_dev_num, NULL, led_drv);if (IS_ERR(device)) {return PTR_ERR(device);}return 0;
}module_init(led_drv_cdev_init);为什么要使用创建类和设备因为不使用的话就需要自己加载模块后创建使用以下指令创建设备节点文件:
mknod /dev/led_drv c 200 0“c”表示这是个字符设备“200”是设备的主设备号“0”是设备的次设备号。主设备号不一定是200我是动态生成的需要在内核加载信息中去查看因为我在代码中动态申请设备号后写了个打印使用如下指令
demsg | tail但是这样没有创建类和设备方便所以就使用创建类和设备了。
编写出口函数
出口函数就是我们在注销设备的时候会执行的函数一般我们需要注销我们前面定义的字符设备。
static void __exit led_drv_cdev_exit(void)
{// 释放字符设备cdev_del(led_cdev);unregister_chrdev_region(led_dev_num, 1);//删除类和设备device_destroy(class, led_dev_num);class_destroy(class);
}module_exit(led_drv_cdev_exit);出口入口函数完善引脚初始化
我们把框架搭好后就可以开始初始化引脚相关的寄存器了其实操作寄存器的方式和单片机感觉很像但是内核不能直接操作寄存器物理地址我们需要先把寄存器相关地址映射到虚拟地址然后进行操作。
首先就是虚拟地址的映射具体操作相关如下映射后我们才能使用相关的寄存器。
// 寄存器地址
#define BCM2837_GPIO_FSEL0_BASE 0x3F200000 // GPIO功能选择寄存器0
#define BCM2837_GPIO_FSEL1_BASE 0x3F200004 // GPIO功能选择寄存器1
#define BCM2837_GPIO_FSEL2_BASE 0x3F200008 // GPIO功能选择寄存器2
#define BCM2837_GPIO_SET0_BASE 0x3F20001C // GPIO置位寄存器0
#define BCM2837_GPIO_CLR0_BASE 0x3F200028 // GPIO清零寄存器0
#define BCM2837_GPIO_LEV0_BASE 0x3F200034 // GPIO清零寄存器0 // 寄存器对应的虚拟ioremap后的地址会在后面初始化
static void __iomem *BCM2837_GPIO_FSEL0 NULL;
static void __iomem *BCM2837_GPIO_FSEL1 NULL;
static void __iomem *BCM2837_GPIO_FSEL2 NULL;
static void __iomem *BCM2837_GPIO_SET0 NULL;
static void __iomem *BCM2837_GPIO_CLR0 NULL;
static void __iomem *BCM2837_GPIO_LEV0 NULL;static int __init led_drv_cdev_init(void)
{// 将树莓派引脚控制相关的寄存器进行虚拟地址映射使得可以控制对应寄存器BCM2837_GPIO_FSEL0 ioremap(BCM2837_GPIO_FSEL0_BASE, 0x04);BCM2837_GPIO_FSEL1 ioremap(BCM2837_GPIO_FSEL1_BASE, 0x04);BCM2837_GPIO_FSEL2 ioremap(BCM2837_GPIO_FSEL2_BASE, 0x04);BCM2837_GPIO_SET0 ioremap(BCM2837_GPIO_SET0_BASE , 0x04);BCM2837_GPIO_CLR0 ioremap(BCM2837_GPIO_CLR0_BASE , 0x04);BCM2837_GPIO_LEV0 ioremap(BCM2837_GPIO_LEV0_BASE , 0x04);// 注册字符设备、创建类和设备// ************** //return 0;
}我们可以使用映射后的寄存器写一些对寄存器的读写操作来控制引脚电平的读写。
// 想要控制的LED灯BCM引脚定义这是设计的扩展板上的定义
#define LED2 17
#define LED3 27
#define LED4 22
#define LED5 23#define LED_OUTPUT 1
#define LED_INPUT 0/*** brief 设置对应引脚的高低电平* param pin 需要设置的引脚* param level 1是高电平 0是低电平*/
void gpio_set_level(int pin, int level)
{// 通过想要的电平判断现在想要控制的寄存器void* reg (level ? BCM2837_GPIO_SET0 : BCM2837_GPIO_CLR0);iowrite32(1 pin, reg);
}/*** brief 获得引脚的电平* param pin 需要获得的引脚* return 1是高电平 0是低电平*/
static int gpio_get_level(int pin)
{int ret 0;int pin_level 0;// 读取引脚电平寄存器pin_level ioread32( BCM2837_GPIO_LEV0 );// 将想要读取的引脚的状态提取出来ret (1 pin );ret ret pin_level;if(ret){return 1;}else{return 0;}
}/*** brief 设置GPIO的寄存器配置GPIO是输入还是输出* param pin 需要设置的引脚* param mode 1是输出模式 0是输入模式*/
static void gpio_set_mode(int pin, int mode)
{void *reg NULL;int val 0;int pin_ctl 0;// 通过pin号来确定要控制的寄存器if(pin 10){reg BCM2837_GPIO_FSEL0;}else if(pin 20){reg BCM2837_GPIO_FSEL1;}else if(pin 30){reg BCM2837_GPIO_FSEL2;}// 比如我要控制11号脚就是要控制BCM2837_GPIO_FSEL1_BASE的1号位置的3个位。pin_ctl pin % 10;// 将对应的gpio的功能选择位全部写0val ~(7 (pin_ctl * 3));val ioread32(reg);// 控制设置对应的gpio的功能选择位写 000 还是 001val | (mode (pin_ctl * 3));iowrite32(val, reg);
}然后在初始化的时候我们就可以初始化我们的引脚
static int __init led_drv_cdev_init(void)
{// 寄存器进行虚拟地址映射// ************** //// 控制引脚输入输出状态gpio_set_mode(LED2, LED_OUTPUT);gpio_set_mode(LED3, LED_OUTPUT);gpio_set_mode(LED4, LED_OUTPUT);gpio_set_mode(LED5, LED_OUTPUT);// 设置引脚电平gpio_set_level(LED2, 0);gpio_set_level(LED3, 0);gpio_set_level(LED4, 0);gpio_set_level(LED5, 0);// 注册字符设备、创建类和设备// ************** //return 0;
}在出口函数完善相关引脚处理主要是释放内存映射。
static void __exit led_drv_cdev_exit(void)
{// 设置电平为高gpio_set_level(LED2, 1);gpio_set_level(LED3, 1);gpio_set_level(LED4, 1);gpio_set_level(LED5, 1);// 取消gpio物理内存映射iounmap(BCM2837_GPIO_FSEL0);iounmap(BCM2837_GPIO_FSEL1);iounmap(BCM2837_GPIO_FSEL2);iounmap(BCM2837_GPIO_SET0);iounmap(BCM2837_GPIO_CLR0);iounmap(BCM2837_GPIO_LEV0);// 释放字符设备、删除类和设备// ************** //
}设备操作函数完善逻辑
我们之前不是写了write和read的操作函数吗现在就需要完善这些操作函数使得用户文件在调用这个文件的时候可以操作gpio进行控制。
先解释读函数这里比较重要的操作就是copy_to_user他会把第二个参数里面的值复制到第一个参数中去这里的buf就是我们用户层想读的值len是用户写的长度。
#define MIN(a, b) (a b ? a : b)// 通过文件读取得到当前LED的状态
ssize_t led_drv_read(struct file* filp, char __user* buf, size_t len, loff_t* off)
{int ret 0;int char_len 0;char led_state[4];printk(%s %s line %d\r\n, __FILE__, __FUNCTION__, __LINE__);led_state[0] gpio_get_level(LED2);led_state[1] gpio_get_level(LED3);led_state[2] gpio_get_level(LED4);led_state[3] gpio_get_level(LED5);char_len sizeof(led_state);int real_len MIN(len,char_len);ret copy_to_user(buf, led_state, real_len);return ret 0 ? ret : real_len;
}然后是写函数其实就和读差不多了主要是copy_from_user能够把用户传进的值进行复制处理。
// 通过向文件写入LED状态控制LED灯
ssize_t led_drv_write(struct file* filp, const char __user* buf, size_t len, loff_t* off)
{int ret 0;char led_state[4];int char_len 0;printk(%s %s line %d\r\n, __FILE__, __FUNCTION__, __LINE__);char_len sizeof(led_state);int real_len MIN(len,char_len);ret copy_from_user(led_state, buf, real_len);switch (led_state[0]){case 0:gpio_set_level(LED2, led_state[1]);break; case 1:gpio_set_level(LED3, led_state[1]);break;case 2:gpio_set_level(LED4, led_state[1]);break;case 3:gpio_set_level(LED5, led_state[1]);break;default:break;}return 0;
}到这里这个驱动文件就编写完成了后面就是编译加载了。
编写应用层函数
驱动编写完成后我们需要有一个应用层函数来调用这个驱动写出以下示例。主要功能就是打开驱动写和读驱动的内容。
#include sys/types.h
#include sys/stat.h
#include fcntl.h
#include unistd.h
#include stdio.h
#include string.h
#include stdlib.h
#include sys/ioctl.h/** sudo ./led_drv_cdev_app -w 0 0* sudo ./led_drv_cdev_app -r*/int main(int argc, char **argv)
{int fd;char writeBuff[3];char readBuff[5];int len;if (argc 2){printf(Usage: %s -w string\n, argv[0]);printf( %s -r\n, argv[0]);return -1;}fd open(/dev/led_drv, O_RDWR);if (fd -1){printf(can not open file /dev/led_drv\n);return -1;}if ((0 strcmp(argv[1], -w)) (argc 4)){writeBuff[0] (char)atoi(argv[2]);writeBuff[1] (char)atoi(argv[3]);writeBuff[2] \0;printf(write : %d, %d\n, writeBuff[0], writeBuff[1]);write(fd, writeBuff, 3);}else if((0 strcmp(argv[1], -r)) (argc 2)){len read(fd, readBuff, 5);readBuff[4] \0;printf(pin read : 0x%x, 0x%x, 0x%x, 0x%x\n, readBuff[0], readBuff[1], readBuff[2], readBuff[3]);}else{printf(APP Failed\n);}close(fd);return 0;
}编译加载
makefile主要参考正点原子和韦东山的写法。这个Makefile会把驱动模块和应用程序一起编译。
# 模块驱动必须以obj-mxxx形式编写
obj-m led_drv_cdev.oKDIR /home/linux-rpi-6.6.y/
CROSS ARCHarm CROSS_COMPILEarm-linux-gnueabihf-
CROSS_COMPILE arm-linux-gnueabihf-all:$(MAKE) -C $(KDIR) M$(PWD) $(CROSS) modules$(CROSS_COMPILE)gcc -o led_drv_cdev_app led_drv_cdev_app.c.PHONY: clean
clean:$(MAKE) -C $(KDIR) Mpwd $(CROSS) cleanmakefile编译完成后直接make就行
make将make生成的文件如我上文写了脚本通过scp传到树莓派上。具体的内容参数自己修改。
#!/bin/shsendfileled_drv_cdev.ko led_drv_cdev_app
pi_userpi
pi_ip192.168.2.149
pi_dir/home/pi/RpiDriver/02_led_drv_cdevscp ${sendfile} ${pi_user}${pi_ip}:${pi_dir}在树莓派上我们需要加载并使用这个模块。
sudo insmod led_drv_cdev.kosudo ./led_drv_cdev_app -w 0 1
sudo ./led_drv_cdev_app -rsudo rmmod led_drv_cdev可以使用dmesg查看运行过程中的变化
dmesg | tail
文章转载自: http://www.morning.dxqfh.cn.gov.cn.dxqfh.cn http://www.morning.tldfp.cn.gov.cn.tldfp.cn http://www.morning.rqmqr.cn.gov.cn.rqmqr.cn http://www.morning.qyrnp.cn.gov.cn.qyrnp.cn http://www.morning.nfdty.cn.gov.cn.nfdty.cn http://www.morning.yskhj.cn.gov.cn.yskhj.cn http://www.morning.nyhtf.cn.gov.cn.nyhtf.cn http://www.morning.yhplt.cn.gov.cn.yhplt.cn http://www.morning.rysmn.cn.gov.cn.rysmn.cn http://www.morning.bpmnx.cn.gov.cn.bpmnx.cn http://www.morning.nkcfh.cn.gov.cn.nkcfh.cn http://www.morning.rzcbk.cn.gov.cn.rzcbk.cn http://www.morning.shinezoneserver.com.gov.cn.shinezoneserver.com http://www.morning.hflrz.cn.gov.cn.hflrz.cn http://www.morning.xbhpm.cn.gov.cn.xbhpm.cn http://www.morning.rkypb.cn.gov.cn.rkypb.cn http://www.morning.ggrzk.cn.gov.cn.ggrzk.cn http://www.morning.plxhq.cn.gov.cn.plxhq.cn http://www.morning.pqwhk.cn.gov.cn.pqwhk.cn http://www.morning.mcmpq.cn.gov.cn.mcmpq.cn http://www.morning.wypyl.cn.gov.cn.wypyl.cn http://www.morning.ndrzq.cn.gov.cn.ndrzq.cn http://www.morning.rshs.cn.gov.cn.rshs.cn http://www.morning.xdpjf.cn.gov.cn.xdpjf.cn http://www.morning.qrpx.cn.gov.cn.qrpx.cn http://www.morning.lwnb.cn.gov.cn.lwnb.cn http://www.morning.wdwfm.cn.gov.cn.wdwfm.cn http://www.morning.rqhbt.cn.gov.cn.rqhbt.cn http://www.morning.btblm.cn.gov.cn.btblm.cn http://www.morning.fmkjx.cn.gov.cn.fmkjx.cn http://www.morning.dkqyg.cn.gov.cn.dkqyg.cn http://www.morning.nxhjg.cn.gov.cn.nxhjg.cn http://www.morning.pdkht.cn.gov.cn.pdkht.cn http://www.morning.rqdx.cn.gov.cn.rqdx.cn http://www.morning.pqwhk.cn.gov.cn.pqwhk.cn http://www.morning.tpfny.cn.gov.cn.tpfny.cn http://www.morning.pmsl.cn.gov.cn.pmsl.cn http://www.morning.zfcfx.cn.gov.cn.zfcfx.cn http://www.morning.ncwgt.cn.gov.cn.ncwgt.cn http://www.morning.qcbhb.cn.gov.cn.qcbhb.cn http://www.morning.tbqdm.cn.gov.cn.tbqdm.cn http://www.morning.nxrgl.cn.gov.cn.nxrgl.cn http://www.morning.srbfp.cn.gov.cn.srbfp.cn http://www.morning.pmlgr.cn.gov.cn.pmlgr.cn http://www.morning.hnhkz.cn.gov.cn.hnhkz.cn http://www.morning.fwkq.cn.gov.cn.fwkq.cn http://www.morning.rfbpq.cn.gov.cn.rfbpq.cn http://www.morning.sxcwc.cn.gov.cn.sxcwc.cn http://www.morning.mlpmf.cn.gov.cn.mlpmf.cn http://www.morning.qcymf.cn.gov.cn.qcymf.cn http://www.morning.kdnrp.cn.gov.cn.kdnrp.cn http://www.morning.tnyanzou.com.gov.cn.tnyanzou.com http://www.morning.nkjxn.cn.gov.cn.nkjxn.cn http://www.morning.fkrzx.cn.gov.cn.fkrzx.cn http://www.morning.wnwjf.cn.gov.cn.wnwjf.cn http://www.morning.rykx.cn.gov.cn.rykx.cn http://www.morning.ryyjw.cn.gov.cn.ryyjw.cn http://www.morning.fnmgr.cn.gov.cn.fnmgr.cn http://www.morning.cgthq.cn.gov.cn.cgthq.cn http://www.morning.ktrh.cn.gov.cn.ktrh.cn http://www.morning.lrdzb.cn.gov.cn.lrdzb.cn http://www.morning.ns3nt8.cn.gov.cn.ns3nt8.cn http://www.morning.zhengdaotang.cn.gov.cn.zhengdaotang.cn http://www.morning.rtpw.cn.gov.cn.rtpw.cn http://www.morning.dkqyg.cn.gov.cn.dkqyg.cn http://www.morning.fldk.cn.gov.cn.fldk.cn http://www.morning.stlgg.cn.gov.cn.stlgg.cn http://www.morning.wkjzt.cn.gov.cn.wkjzt.cn http://www.morning.jqzns.cn.gov.cn.jqzns.cn http://www.morning.seoqun.com.gov.cn.seoqun.com http://www.morning.krrjb.cn.gov.cn.krrjb.cn http://www.morning.qymqh.cn.gov.cn.qymqh.cn http://www.morning.bpttm.cn.gov.cn.bpttm.cn http://www.morning.rqnzh.cn.gov.cn.rqnzh.cn http://www.morning.ydrn.cn.gov.cn.ydrn.cn http://www.morning.yrcxg.cn.gov.cn.yrcxg.cn http://www.morning.wjqbr.cn.gov.cn.wjqbr.cn http://www.morning.sqxr.cn.gov.cn.sqxr.cn http://www.morning.dbsch.cn.gov.cn.dbsch.cn http://www.morning.qjghx.cn.gov.cn.qjghx.cn