当前位置: 首页 > news >正文

asp企业网站模板下载17网站一起做网店app

asp企业网站模板下载,17网站一起做网店app,wordpress 每页 关高,太仓建设银行网站鸽了半年#xff0c;几乎全忘了#xff0c;幸亏前面还有两篇总结。出于快速体验嵌入式linux的目的#xff0c;本篇与前两篇一样#xff0c;重点在于使用、快速体验#xff0c;uboot、linux、根文件系统不作深入理解#xff0c;能用就行。 重新梳理一下脉络#xff0c;本…         鸽了半年几乎全忘了幸亏前面还有两篇总结。出于快速体验嵌入式linux的目的本篇与前两篇一样重点在于使用、快速体验uboot、linux、根文件系统不作深入理解能用就行。         重新梳理一下脉络本章学习的是linux驱动开发主要内容涉及到怎么编写linux驱动、怎么编译、怎么加载卸载。在此之前还需要准备两件事其一制作SD卡启动因为由于时间有些久实在是忘了。其二为以太网连接先前由于以太网被占用所以无论是移植还是驱动开发使用的都是SD卡没有用nfs挂载但以太网终究要学习且更加方便。         那么如今目的很简单先简单制作一张能用的SD卡启动对前面内容的巩固与复习。然后再测试以太网通信以便后面从虚拟机编译的模块.ko文件可以传输到开发板上的根文件系统里。接着开始正式编写模块驱动使用到的是linux内核的头文件再熟悉一些开发驱动用到的宏、函数、命名规则以及开发规则即可。最后是加载并测试驱动。         驱动模块开发是我们的主要目的驱动开发有两种方式一种是动态加载驱动模块一种是静态编译驱动到内核。前者开发的是.ko文件可以随时使用insmod加载到正在运行的linux上。后者如其名在linux工程里创建驱动的.c文件然后编译成镜像。由于linux编译时间过长前者可以快速开发并测试驱动节约大量编译时间测试完毕后就可以通过后者添加到linux驱动里了         本应如此遇到问题解决问题但实际上遇到一些大坑而不得不学习更多的内容虽麻烦些但印象却也深刻了许多。注意本文的叙述顺序并非依据标准的知识点总结框架而是按照笔者个人的学习历程展开。 一、制作SD卡启动 1删除所有分区  ①使用ls命令确认SD卡设备       先插上SD卡并选择连接到虚拟机上        接着使用ls命令来列出所有sd设备然后再拔掉SD卡设备列出所有sd设备 ls /dev/sd*         缺少哪一个哪个就是SD卡此处缺失/dev/sdb和/dev/sdb1那么/dev/sdb就是SD卡设备 ②使用fdisk命令删除SD卡所有分区         使用fdisk命令进入fdisk工具界面 sudo fdisk /dev/sdb         使用命令p打印所有分区         输入d命令删除分区由于只有一个分区默认直接删除。 2制作分区         如同前一篇博客所言SD卡的起始位置需要先空10MB供裸机程序uboot存放还需再制作两个分区一个存放linux镜像另一个存放根文件系统。 ①制作linux分区         在刚才的fdisk工具界面内输入命令n来创建分区接着按回车选择默认分区格式p再输入2048020480*512Byte10MB设置起始扇区最后输入500M确定创建分区的大小 ②制作根文件系统分区         输入命令p打印分区可以看到分区1所占扇区的位置为20480~1044479所以第二个分区的起始扇区可以设为1044480紧挨着第一个分区         除了起始扇区的位置外其余按回车选择默认         从最后打印的分区可以看到被薅走的1.5GB存储 ③保存退出         输入命令w即可。不过这里出了一点点小意外         不过却并不影响分区创建 ④为分区设定文件系统格式         先使用ls命令列出所有sd设备 ls /dev/sd*         刚列出来时并没有分区2/dev/sdb2重新插拔SD卡设备后再重新列出就有了         使用下面命令分别为两个分区设置格式FAT和EXT4 sudo mkfs.vfat /dev/sdb1 sudo mkfs.ext4 /dev/sdb2         第二个分区需要等待一段时间制作好后左侧就会出现两个USB一样的图标 3烧录uboot ①编译uboot ②烧录uboot         使用烧录工具下图使用的烧录工具是基于正点原子提供的工具的改版imxdownload烧写工具         出现下面错误是因为这个工具在编写时使用的是C来创建文件而非本地的linux命令故而需要在前面添加sudo命令 sudo ./imx_download -b u-boot.bin -s /dev/sdb 4拷贝linux镜像和设备树 ①编译linux         在Makefile已经指定架构和编译器的情况下运行脚本 #!/bin/bash# 通过chmod x build.sh赋予权限# 函数定义用于执行不同的make命令 make_distclean() {echo 执行 make distcleanmake distclean }make_imx_v7_defconfig() {echo 执行 make imx_v7_defconfig -j16make imx_v7_defconfig -j16 }make_all() {echo 执行 make -j16make -j16 }make_menuconfig() {echo 执行 make menuconfigmake menuconfig }# 当没有参数时执行所有命令 if [ $# -eq 0 ]; thenecho 没有参数执行所有命令make_distcleanmake_imx_v7_defconfig # make_menuconfigmake_allelse# 主逻辑根据输入参数调用相应的函数case $1 inc)make_distclean;;d)make_imx_v7_defconfig;;a)make_all;;m)make_menuconfig;;*)echo 无效的参数: $1echo 用法: $0 [{c|d|a|m}]exit 1;;esac fi 根据提示找到镜像指定路径 ②拷贝         为了把镜像和设备树拷贝到SD卡中先创建一个目录/mnt把SD卡挂载到上面然后再把镜像和设备树复制到/mnt目录 sudo mount /dev/sdb1 /mnt         挂载后左边的USB图标就会少掉一个 sudo cp arch/arm/boot/zImage /mnt         设备树就在arch/arm/boot/dts目录进入后寻找到匹配的dtb文件然后复制到/mnt目录中 sudo cp ./imx6ull-14x14-emmc-7-1024x600-c.dtb /mnt 使用sync后然后再取消挂载 sudo umount /dev/sdb1 5拷贝根文件系统 ①传输根文件系统         使用FileZilla传输文件调了好半天NAT是给虚拟机上网用的桥接是给以太网用的。虚拟机能ping主机不行控制面板启用VMnet8。         开发资料A盘里有些根文件系统不能正常使用不过笔者没有一一尝试下面这个根文件系统是正常的 ②拷贝         把传输的压缩包复制到已经挂载SD卡第二个分区的/mnt里然后解压 sudo tar -xvjf rootfs.tar.bz2         解压后删除压缩包然后使用sync同步最后取消挂载 6、启动开发板          串口连接至电脑插上SD卡后拨码选择SD启动。然后进入uboot设置启动命令 setenv bootcmd load mmc 0:1 0x83000000 zimage; load mmc 0:1 0x83800000 imx6ull-14x14-emmc-7-1024x600-c.dtb; bootz 0x83000000 - 0x83800000        保存启动命令后重新复位 saveenv         本来想自行动态计算地址结果发现uboot的运算有问题格式正确也会报语法错误。后来用/0x1000和*0x1000来代替但会一直卡在启动内核步骤。最后还是用回了以前的命令这个先搁置 。 最终效果如下除了壁纸中部细看略微有些条纹外一切正常(可能这是特点?) 二、网络连接 1设置ip和子网掩码 ①测试uboot         开发板上电后按下任意键进入uboot里通过下面命令设置开发板的ip、子网掩码和MACMAC地址不能重复 setenv ipaddr 192.168.1.254 setenv netmask 255.255.255.0 setenv ethaddr 00:11:22:33:44:55         然后设置主机的ip         setenv serverip 192.168.1.255         最后保存 saveenv         这里为了避免ip抢占就把开发板和主机的ip设置得比较远当然也可以不用192.168.1这个网段。         非常奇怪的是无论去ping虚拟机还是ping开发板自身都会出现下面数据错误使用的是同一个u-boot以前并未发生过。         不过还是找到了相关博客uboot下出现data abort错误导致重启解决办法         在uboot工程里的arch/arm/cpu/armv7/start.S 中第130行左右按照博客里的去修改。不得不说大佬就是大佬错误直接解决了 ②设置linux         修改下面文件设置eth0为静态IPIP地址随意需要注意本篇后面其实使用的其实都是192.168.1.127但图是192.168.1.254 sudo vi /etc/network/interfaces         进入后把iface etho inet dhcp改为下面都是vim的基本操作 auto eth0 iface eth0 inet staticaddress 192.168.1.127 # 开发板的静态IPnetmask 255.255.255.0 # 子网掩码gateway 192.168.1.1 # 网关dns-nameservers 8.8.8.8 # DNS服务器 图中乱码可能是显示的问题 修改完后使用下面命令来重启网络 sudo /etc/init.d/networking restart         此时ip地址已被正确设置         ping虚拟机可以看到一切正常此时虚拟机的ip设为192.168.1.128因为192.168.1.255是广播地址还需要加上-b参数         按CtrlC可以暂停操作。         可以看到虚拟机也能ping通开发板 2建立连接         这里能使用的方法有很多         这里使用的是NFS主机和开发板需要各自配置后才能进行正常通信 ①主机         先安装nfs服务 sudo apt-get install nfs-kernel-server         创建一个共享目录并赋予权限比如在用户目录里创建user自行替换 mkdir /home/user/nfs_share chmod 777 /home/user/nfs_share         编辑NFS配置文件 /etc/exports添加共享目录和权限 sudo vim /etc/exports /home/user/nfs_share 192.168.1.0/24(rw,sync,no_subtree_check,no_root_squash)         重启NFS服务 sudo systemctl restart nfs-kernel-server         检查NFS共享是否生效 sudo exportfs -v ②主机给开发板联网         开发板需要下载nfs客户端需要联网。联网可以使用IP转发但这个有些麻烦以后再说可以直接使用Windows的网络共享功能参考博客开发板和笔记本网线连接         到控制面板里找到网络和Internet再点击网络和共享中心进入下面步骤点击更改适配器设置         右键WLAN笔记本的一个网口已经通过以太网线与开发板连接所以用的是WiFi按如下设置         共享之后使用ifconifg查看ip         设置完后在开发板的linux里ping百度网址 ping www.baidu.com         联网是没有问题但是这个ping出来的结果很慢需要耐心等待 ③开发板         既然可以联网那么接下来需要开发板下载nfs客户端。不过这个根文件系统的apt没有资源列表需要手动创建 touch /etc/apt/sources.list         然后是添加网址这里使用的是阿里镜像源可自行替换需要的源 vi /etc/apt/sources.list deb http://mirrors.aliyun.com/ubuntu/ bionic main restricted universe multiverse deb http://mirrors.aliyun.com/ubuntu/ bionic-security main restricted universe multiversedeb http://mirrors.aliyun.com/ubuntu/ bionic-updates main restricted universe multiverse deb http://mirrors.aliyun.com/ubuntu/ bionic-proposed main restricted universe multiversedeb http://mirrors.aliyun.com/ubuntu/ bionic-backports main restricted universe multiversedeb-src http://mirrors.aliyun.com/ubuntu/ bionic main restricted universe multiverse deb-src http://mirrors.aliyun.com/ubuntu/ bionic-security main restricted universe multiversedeb-src http://mirrors.aliyun.com/ubuntu/ bionic-updates main restricted universe multiverse deb-src http://mirrors.aliyun.com/ubuntu/ bionic-proposed main restricted universe multiversedeb-src http://mirrors.aliyun.com/ubuntu/ bionic-backports main restricted universe multiverse         现在使用apt-get update就正常多了不再会任何列表都没有了         到这一步都还挺顺利的但无法使用apt下载。询问技术客服他们说正点原子的根文件系统使用的是Yocto构建的对apt的支持并不完善。前面为了快速体验开发 只学习了怎么移植并没有学习怎么制作根文件系统。         询问了DeepSeek虽然apt功能强大但对于嵌入式来说OPKG和RPM更适合Yocto支持。既如此制作根文件系统先放一放学Qt制作桌面时应该会用到。         使用下面命令检查根文件系统是否支持NFS从结果来看是支持的应该是正点原子在已经提前移植过这些库了。那么就不需要安装nfs客户端了包管理器下载先放一放。 which mount.nfs                           需要注意的是前面为了联网启用了网络共享功能现在要尝试nfs挂载那么就要关闭共享。同时使用ifconfig来查看ip是否正确如果不正确那么重启一下网络再检查ip地址确保开发板和虚拟机能互ping。开发板每次重启还需要输入下面命令来重启网络 sudo /etc/init.d/networking restart         前面在主机上使用的ip是192.168.1.128路径是/home/user/nfs_share下面就可以据此来建立nfs通信了现在开发板上创建一个用于挂载的目录比如/mnt/nfs mkdir /mnt/nfs         本来应该使用下面这个命令建立连接的用户名和ip自行替换但需要指定版本 mount -t nfs 192.168.1.128:/home/fairy/nfs_share /mnt/nfs         在此之前我们可以先试用下面命令来查看挂载点 df -h         再使用添加了版本的挂载命令如果版本3不行试试4该命令没有任何提示 mount -t nfs -o nfsvers3 192.168.1.128:/home/fairy/nfs_share /mnt/nfs         再使用df -h可以看到已经成功挂载了 3测试nfs         刚才在联网的情况下顺便又测试了下包管理器没想到opkg、rpm、dpkg一个能用的都没有技术客服说需要手动管理。         使用ls查看虚拟机和开发板的挂载点可以看到任何内容都没有         在虚拟机的挂载目录里随便创建一个文件         可以看到开发板的挂载点里确实多了一个文件挂载成功 启用ip转发废稿         编辑开发板的网络配置文件 /etc/network/interfaces sudo vi /etc/network/interfaces         把网关改为虚拟机ip 192.168.1.128(这里把开发板的ip改为了192.168.1.127 auto eth0 iface eth0 inet staticaddress 192.168.1.127netmask 255.255.255.0gateway 192.168.1.128dns-nameservers 8.8.8.8         重启网络驱动开发板每次重启后还得手动重启网络 sudo /etc/init.d/networking restart         编辑 /etc/resolv.conf 文件修改DNS配置 sudo vi /etc/resolv.conf         修改为下面内容开发板每次重启都会覆盖掉下面内容 nameserver 8.8.8.8 nameserver 114.114.114.114 三、驱动编写_基础         回顾一下前面折腾了那么久无论是制作SD卡启动还是使用nfs挂载本质上都是为驱动编写提供便利条件本篇最终目标“驱动编写”并没有变。         事实上学到现在这个程度对linux的使用和搭建都有了一些基本的了解和熟悉看视频不再是首选文档是更推荐的选择正点原子的文档质量很高。可以从“跟随式”学习转为“主动学习”知道要实现什么样的应用或解决什么样的问题为此需要学习哪些内容学习过程遇到问题怎么解决怎么取舍。发问那么问题就已经解决了一半遇到问题解决问题那么学习路径就确立了。 1动态加载_基础方式         文档是先做字符设备开发再做LED驱动开发循序渐进。不过直接做LED驱动也行可以更快地看到实验结果两者区别并不大没有太大的难度壁垒。         这个过程可以分为两个步骤其一编写驱动、生成.ko文件、加载卸载驱动其二为测试编写一个应用程序生成elf文件通过运行程序来观察结果。注意多翻阅文档手册 ①从源码入手         找到开发盘里的led驱动通过filezila传输到虚拟机中         在虚拟机中用自己的IDE打开刚才传输的工程         需要修改一下Makefile里的路径KERNELDIR 换成自己linux内核的目录。如果是CLion的话根据错误提示把构建目标all换成build或者在Makefile里把build改为all         直接构建没有任何问题 ②分析源码框架         下面是正点原子的led源码 #include linux/types.h #include linux/kernel.h #include linux/delay.h #include linux/ide.h #include linux/init.h #include linux/module.h #include linux/errno.h #include linux/gpio.h #include asm/mach/map.h #include asm/uaccess.h #include asm/io.h /*************************************************************** Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved. 文件名 : led.c 作者 : 左忠凯 版本 : V1.0 描述 : LED驱动文件。 其他 : 无 论坛 : www.openedv.com 日志 : 初版V1.0 2019/1/30 左忠凯创建 ***************************************************************/ #define LED_MAJOR 200 /* 主设备号 */ #define LED_NAME led /* 设备名字 */#define LEDOFF 0 /* 关灯 */ #define LEDON 1 /* 开灯 *//* 寄存器物理地址 */ #define CCM_CCGR1_BASE (0X020C406C) #define SW_MUX_GPIO1_IO03_BASE (0X020E0068) #define SW_PAD_GPIO1_IO03_BASE (0X020E02F4) #define GPIO1_DR_BASE (0X0209C000) #define GPIO1_GDIR_BASE (0X0209C004)/* 映射后的寄存器虚拟地址指针 */ static void __iomem *IMX6U_CCM_CCGR1; static void __iomem *SW_MUX_GPIO1_IO03; static void __iomem *SW_PAD_GPIO1_IO03; static void __iomem *GPIO1_DR; static void __iomem *GPIO1_GDIR;/** description : LED打开/关闭* param - sta : LEDON(0) 打开LEDLEDOFF(1) 关闭LED* return : 无*/ void led_switch(u8 sta) {u32 val 0;if (sta LEDON){val readl(GPIO1_DR);val ~(1 3);writel(val, GPIO1_DR);} else if (sta LEDOFF){val readl(GPIO1_DR);val | (1 3);writel(val, GPIO1_DR);} }/** description : 打开设备* param - inode : 传递给驱动的inode* param - filp : 设备文件file结构体有个叫做private_data的成员变量* 一般在open的时候将private_data指向设备结构体。* return : 0 成功;其他 失败*/ static int led_open(struct inode *inode, struct file *filp) {return 0; }/** description : 从设备读取数据* param - filp : 要打开的设备文件(文件描述符)* param - buf : 返回给用户空间的数据缓冲区* param - cnt : 要读取的数据长度* param - offt : 相对于文件首地址的偏移* return : 读取的字节数如果为负值表示读取失败*/ static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt) {return 0; }/** description : 向设备写数据* param - filp : 设备文件表示打开的文件描述符* param - buf : 要写给设备写入的数据* param - cnt : 要写入的数据长度* param - offt : 相对于文件首地址的偏移* return : 写入的字节数如果为负值表示写入失败*/ static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt) {int retvalue;unsigned char databuf[1];unsigned char ledstat;retvalue copy_from_user(databuf, buf, cnt);if (retvalue 0){printk(kernel write failed!\r\n);return -EFAULT;}ledstat databuf[0]; /* 获取状态值 */if (ledstat LEDON){led_switch(LEDON); /* 打开LED灯 */} else if (ledstat LEDOFF){led_switch(LEDOFF); /* 关闭LED灯 */}return 0; }/** description : 关闭/释放设备* param - filp : 要关闭的设备文件(文件描述符)* return : 0 成功;其他 失败*/ static int led_release(struct inode *inode, struct file *filp) {return 0; }/* 设备操作函数 */ static struct file_operations led_fops {.owner THIS_MODULE,.open led_open,.read led_read,.write led_write,.release led_release, };/** description : 驱动出口函数* param : 无* return : 无*/ static int __init led_init(void) {int retvalue 0;u32 val 0;/* 初始化LED *//* 1、寄存器地址映射 */IMX6U_CCM_CCGR1 ioremap(CCM_CCGR1_BASE, 4);SW_MUX_GPIO1_IO03 ioremap(SW_MUX_GPIO1_IO03_BASE, 4);SW_PAD_GPIO1_IO03 ioremap(SW_PAD_GPIO1_IO03_BASE, 4);GPIO1_DR ioremap(GPIO1_DR_BASE, 4);GPIO1_GDIR ioremap(GPIO1_GDIR_BASE, 4);/* 2、使能GPIO1时钟 */val readl(IMX6U_CCM_CCGR1);val ~(3 26); /* 清楚以前的设置 */val | (3 26); /* 设置新值 */writel(val, IMX6U_CCM_CCGR1);/* 3、设置GPIO1_IO03的复用功能将其复用为* GPIO1_IO03最后设置IO属性。*/writel(5, SW_MUX_GPIO1_IO03);/*寄存器SW_PAD_GPIO1_IO03设置IO属性*bit 16:0 HYS关闭*bit [15:14]: 00 默认下拉*bit [13]: 0 kepper功能*bit [12]: 1 pull/keeper使能*bit [11]: 0 关闭开路输出*bit [7:6]: 10 速度100Mhz*bit [5:3]: 110 R0/6驱动能力*bit [0]: 0 低转换率*/writel(0x10B0, SW_PAD_GPIO1_IO03);/* 4、设置GPIO1_IO03为输出功能 */val readl(GPIO1_GDIR);val ~(1 3); /* 清除以前的设置 */val | (1 3); /* 设置为输出 */writel(val, GPIO1_GDIR);/* 5、默认关闭LED */val readl(GPIO1_DR);val | (1 3);writel(val, GPIO1_DR);/* 6、注册字符设备驱动 */retvalue register_chrdev(LED_MAJOR, LED_NAME, led_fops);if (retvalue 0){printk(register chrdev failed!\r\n);return -EIO;}return 0; }/** description : 驱动出口函数* param : 无* return : 无*/ static void __exit led_exit(void) {/* 取消映射 */iounmap(IMX6U_CCM_CCGR1);iounmap(SW_MUX_GPIO1_IO03);iounmap(SW_PAD_GPIO1_IO03);iounmap(GPIO1_DR);iounmap(GPIO1_GDIR);/* 注销字符设备驱动 */unregister_chrdev(LED_MAJOR, LED_NAME); }module_init(led_init);module_exit(led_exit);MODULE_LICENSE(GPL); MODULE_AUTHOR(zuozhongkai);结合文档和源码我们可以看到一个驱动模块开发应该包含一下内容 包含内核头文件 定义设备操作函数结构体 实现设备操作函数 定义模块初始化和退出函数 定义模块信息         此外还需要遵循一些特定的规范比如 模块信息通常放在文件的末尾紧挨着模块的初始化和退出函数。 必须定义 MODULE_LICENSE其他模块信息如作者、描述、版本号是可选的但建议尽量提供。 ……         一个简单的模块示例如下 #include linux/module.h #include linux/init.hstatic int my_open(void) {/*……*/ }/*……*/static struct file_operations my_fops {.owner THIS_MODULE, // 指向当前模块.open my_open, // 打开设备.read my_read, // 读取设备.write my_write, // 写入设备.release my_release, // 关闭设备 };static int __init my_init(void) {printk(KERN_INFO Module loaded\n);return 0; }static void __exit my_exit(void) {printk(KERN_INFO Module unloaded\n); }module_init(my_init); module_exit(my_exit);MODULE_LICENSE(GPL); MODULE_AUTHOR(Your Name); MODULE_DESCRIPTION(A simple example module); MODULE_VERSION(1.0); ③分析源码细节人机对战 为什么要用到ioremap 和 iounmap         Linux 内核运行在虚拟地址空间MMU无法直接访问物理地址。ioremap 的作用就是将硬件的物理地址映射到内核的虚拟地址空间使得内核可以通过虚拟地址访问硬件寄存器。ioremap 返回的是虚拟地址指针。         ioremap 的作用是释放映射的虚拟地址空间当模块卸载时需要释放之前映射的虚拟地址空间以避免内存泄漏。ioremap 和 iounmap 确保内核能够安全地访问硬件资源并在模块卸载时释放资源。         所谓映射可以理解为MMU分配页表什么的所以才需要释放虚拟地址不然虚拟地址空间就会被消耗殆尽。 注册字符驱动设备这个步骤是干什么的         字符设备注册是将驱动程序与设备号关联并在内核中注册设备。注册字符设备后用户空间程序可以通过设备文件如 /dev/led访问设备。 设备号是干什么的为什么要分为主次两种这样做是为了解决什么问题         设备号用于在内核中唯一标识一个设备内核通过设备号管理设备确保多个设备可以共存用户空间程序通过设备文件如 /dev/led访问设备时内核根据设备号找到对应的驱动程序。         主设备号可以标识设备类型如 LED、键盘、串口等主设备号是全局唯一的由内核或开发者分配一个主设备号对应一个驱动程序。         次设备号用于标识具体设备用于区分同一类型的多个设备。比如主设备号为 200 的 LED 设备可能有多个次设备号次设备号 0 表示第一个 LED、次设备号 1 表示第二个 LED。         通过主次设备号的设计Linux 内核能够高效地管理大量设备并为用户空间程序提供统一的访问接口。开发者只需实现一个驱动程序即可支持多个设备实例同一类型的多个设备可以共享一个驱动程序减少了内核资源的浪费。 也就是在驱动模块开发时分配设备号那么加载驱动模块时就可以产生设备文件(如/dev/led那么分配设备号还有哪些方式         在 Linux 驱动模块开发中设备号的分配和设备文件的创建是两个关键步骤。加载驱动模块时设备文件如 /dev/led并不会自动生成而是需要手动创建或通过其他机制自动创建。         设备号的分配分为静态和动态两种动态分配是通过使用 alloc_chrdev_region 函数由内核动态分配设备号。一般可以使用混合分配的方式优先动态分配失败时回退到静态分配这样就可以兼具动态分配的灵活性和静态分配的稳定性。 为什么还要手动或自动创建设备文件为什么不在加载驱动模块时创建呢既然创建设备文件需要主次设备号那我明明在驱动模块开发过程中分配了为何还要在创建设备文件时再分配一次呢         驱动模块加载时内核会初始化驱动程序并分配设备号但并不会自动创建设备文件。设备文件的创建通常由用户空间工具如 mknod或内核机制如 udev、devtmpfs完成。         设备文件的创建可能涉及用户空间的配置如权限、所有者等这些配置不适合在内核中处理。设备文件的创建时机可能需要根据系统状态动态调整。除此之外还有一部分历史原因早期的 Linux 内核没有自动创建设备文件的机制开发者需要手动使用 mknod 创建设备文件。现代 Linux 内核引入了 udev 和 devtmpfs可以自动创建设备文件但仍然保留了手动创建的选项。         两者分离便于职责分离有更大的灵活性。设备号的分配是内核的职责用于管理设备和驱动程序。设备文件的创建是用户空间的职责用于提供用户访问接口。两者分离后设备文件的创建可以根据系统配置动态调整如权限、所有者等可以延迟到设备实际使用时如热插拔设备。         现代 Linux 内核提供了自动创建设备文件的机制开发者无需手动使用 mknod使用 udev 或 devtmpfs 可以自动创建设备文件。udev 是 Linux 的用户空间设备管理器负责管理 /dev 目录下的设备文件当内核检测到新设备时udev 会根据规则自动创建设备文件。驱动程序需要在初始化时调用 class_create 和 device_create 函数向 udev 提供设备信息。 open和write函数有什么区别 在开发stm32的驱动时可以调用各种库函数来辅助开发在嵌入式Linux的驱动开发中只能从寄存器级别开发、不能使用NXP官方提供的库函数吗         在嵌入式 Linux 驱动开发中与 STM32 的开发方式有所不同。STM32 的开发通常依赖于厂商提供的库函数如 HAL 库或标准外设库而在嵌入式 Linux 驱动开发中通常不会直接使用厂商提供的库函数如 NXP 提供的 SDK 库而是通过以下方式操作硬件         寄存器级别开发         使用内核提供的 API         使用设备树Device Tree         使用现成的驱动框架Linux 内核提供了许多现成的驱动框架如 I2C、SPI、USB 等开发者可以基于这些框架实现驱动而无需从零开始。         厂商提供的库函数通常是为裸机或 RTOS 环境设计的而 Linux 内核运行在内核空间对内存管理、中断处理等有严格的要求。如果直接使用厂商库函数可能导致内核崩溃或资源冲突。Linux 内核提供了丰富的 API 来操作硬件这些 API 是专门为内核空间设计的能够更好地与内核的其他部分协同工作。直接操作寄存器或使用内核 API 可以提高驱动的可移植性使其更容易适配不同的内核版本和硬件平台。 …… …… ④编译驱动模块和测试程序         稍微修改一下write代码编译时发现了一个警告万万没想到会出现C90标准         稍微查了一下这是历史原因         但我有点不太相信现代Linux都有使用Rust编写的部分了不可能这般守旧才对         为了兼容性沉重的历史包袱是难免的。在Makefile里添加这一句 # 添加 C11 标准支持 ccflags-y : -stdgnu11 -Wno-declaration-after-statement         为了测试驱动模块还需要编写应用程序此处即ledApp为此编译还需要添加一个目标ledApp KERNELDIR : /home/fairy/Embedded/program/Alientek_Uboot_Linux/linux CURRENT_PATH : $(shell pwd)obj-m : led.o# 添加 C11 标准支持 ccflags-y : -stdgnu11 -Wno-declaration-after-statementall: kernel_modules ledAppkernel_modules:$(MAKE) -C $(KERNELDIR) M$(CURRENT_PATH) modules# 编译应用程序 ledApp: ledApp.carm-linux-gnueabihf-gcc -o $ $clean:$(MAKE) -C $(KERNELDIR) M$(CURRENT_PATH) clean         测试程序的代码很清晰先打开文件再写入数据最后关闭。main函数里的两个参数前者为参数个数后者为参数指针数组。编译的程序为ledApp.elf在linux里elf后缀一般不写运行程序时传入参数是指在命令行中输入程序名称并且在名称后输入一些内容参数比如下面传入的第二个参数是1第一个参数默认都是程序自身的名称 ./ledApp 1         放在程序里下面这一步就是把传入的参数“1”存放到数组databuf里 ⑤测试驱动模块         把驱动模块和测试应用程序都传输到虚拟机的nfs挂载点         在开发板的挂载点里可以看到文件已经成功传入 加载驱动模块 insmod /mnt/nfs/led.ko 列出设备 lsmod 查看设备 cat /proc/devices 查看设备节点 ls -l /dev/         可以看到是没有led设备节点的因为没有创建设备节点驱动模块使用的还是例程源码并没有添加自动创建设备节点udev需要的相关函数。 创建字符设备节点 mknod /dev/led c 200 0         可以看到led设备节点已经创建成功了测试应用程序         测试之前需要关闭led自动闪烁功能 echo none /sys/class/leds/sys-led/trigger         此时输入命令才发现架构不对虚拟机使用的是x86_x64而开发板是arm32应使用交叉编译工具链也就是说前面的Makefile编译ledApp时需要把gcc改为arm-linux-gnueabihf-gcc已改         此时使用下面0和1两个参数测试实验结果与预期相符 ./ledApp /dev/led 1 ./ledApp /dev/led 0         使用rmmod卸载模块时设备节点/dev/led并不会消失还需要使用rm来手动删除 rmmod led.ko rm /dev/led ⑥尝试新方法         前面和DeepSeek对话中可以获知混合分配设备号更推荐udev自动创建设备文件更现代。继续提问还有更多更现代的做法比如驱动和硬件分离不过这要用到设备树一些做法可以先放一放。         下面的代码只要用到了1、5和6其他需要设备树配合 #include linux/types.h #include linux/kernel.h #include linux/delay.h #include linux/ide.h #include linux/init.h #include linux/module.h #include linux/errno.h #include linux/gpio.h #include linux/cdev.h #include linux/device.h #include linux/io.h #include linux/slab.h #include linux/uaccess.h#define LED_MAJOR 200 /* 主设备号 */ #define LED_NAME led /* 设备名字 */#define LEDOFF 0 /* 关灯 */ #define LEDON 1 /* 开灯 *//* 寄存器物理地址 */ #define CCM_CCGR1_BASE (0X020C406C) #define SW_MUX_GPIO1_IO03_BASE (0X020E0068) #define SW_PAD_GPIO1_IO03_BASE (0X020E02F4) #define GPIO1_DR_BASE (0X0209C000) #define GPIO1_GDIR_BASE (0X0209C004)/* 映射后的寄存器虚拟地址指针 */ static void __iomem *IMX6U_CCM_CCGR1; static void __iomem *SW_MUX_GPIO1_IO03; static void __iomem *SW_PAD_GPIO1_IO03; static void __iomem *GPIO1_DR; static void __iomem *GPIO1_GDIR;/* 设备号 */ static dev_t devno; static struct cdev led_cdev; static struct class *led_class; static struct device *led_device;/** description : LED打开/关闭* param - sta : LEDON(0) 打开LEDLEDOFF(1) 关闭LED* return : 无*/ static void led_switch(u8 sta) {u32 val;if (sta LEDON){val readl(GPIO1_DR);val ~(1 3);writel(val, GPIO1_DR);} else if (sta LEDOFF){val readl(GPIO1_DR);val | (1 3);writel(val, GPIO1_DR);} }/** description : 打开设备* param - inode : 传递给驱动的inode* param - filp : 设备文件file结构体有个叫做private_data的成员变量* 一般在open的时候将private_data指向设备结构体。* return : 0 成功;其他 失败*/ static int led_open(struct inode *inode, struct file *filp) {return 0; }/** description : 从设备读取数据* param - filp : 要打开的设备文件(文件描述符)* param - buf : 返回给用户空间的数据缓冲区* param - cnt : 要读取的数据长度* param - offt : 相对于文件首地址的偏移* return : 读取的字节数如果为负值表示读取失败*/ static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt) {return 0; }/** description : 向设备写数据* param - filp : 设备文件表示打开的文件描述符* param - buf : 要写给设备写入的数据* param - cnt : 要写入的数据长度* param - offt : 相对于文件首地址的偏移* return : 写入的字节数如果为负值表示写入失败*/ static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt) {unsigned char databuf[1];int retvalue copy_from_user(databuf, buf, cnt);if (retvalue 0){pr_err(kernel write failed!\r\n);return -EFAULT;}led_switch(databuf[0]);return 0; }/** description : 关闭/释放设备* param - filp : 要关闭的设备文件(文件描述符)* return : 0 成功;其他 失败*/ static int led_release(struct inode *inode, struct file *filp) {return 0; }/* 设备操作函数 */ static struct file_operations led_fops {.owner THIS_MODULE,.open led_open,.read led_read,.write led_write,.release led_release, };/** description : 驱动入口函数* param : 无* return : 无*/ static int __init led_init(void) {u32 val;int retvalue;/* 1、寄存器地址映射 */IMX6U_CCM_CCGR1 ioremap(CCM_CCGR1_BASE, 4);SW_MUX_GPIO1_IO03 ioremap(SW_MUX_GPIO1_IO03_BASE, 4);SW_PAD_GPIO1_IO03 ioremap(SW_PAD_GPIO1_IO03_BASE, 4);GPIO1_DR ioremap(GPIO1_DR_BASE, 4);GPIO1_GDIR ioremap(GPIO1_GDIR_BASE, 4);if (!IMX6U_CCM_CCGR1 || !SW_MUX_GPIO1_IO03 || !SW_PAD_GPIO1_IO03 || !GPIO1_DR || !GPIO1_GDIR){pr_err(ioremap failed!\r\n);retvalue -ENOMEM;goto err_ioremap;}/* 2、使能GPIO1时钟 */val readl(IMX6U_CCM_CCGR1);val ~(3 26); /* 清除以前的设置 */val | (3 26); /* 设置新值 */writel(val, IMX6U_CCM_CCGR1);/* 3、设置GPIO1_IO03的复用功能 */writel(5, SW_MUX_GPIO1_IO03);/* 4、设置GPIO1_IO03的IO属性 */writel(0x10B0, SW_PAD_GPIO1_IO03);/* 5、设置GPIO1_IO03为输出功能 */val readl(GPIO1_GDIR);val ~(1 3); /* 清除以前的设置 */val | (1 3); /* 设置为输出 */writel(val, GPIO1_GDIR);/* 6、默认关闭LED */val readl(GPIO1_DR);val | (1 3);writel(val, GPIO1_DR);/* 7、设备号混合分配 */retvalue alloc_chrdev_region(devno, 0, 1, LED_NAME);if (retvalue 0){pr_err(dynamic alloc chrdev failed, try static alloc!\r\n);devno MKDEV(LED_MAJOR, 0);retvalue register_chrdev_region(devno, 1, LED_NAME);if (retvalue 0){pr_err(static alloc chrdev failed!\r\n);goto err_alloc_chrdev;}}/* 8、初始化 cdev */cdev_init(led_cdev, led_fops);led_cdev.owner THIS_MODULE;/* 9、添加 cdev 到内核 */retvalue cdev_add(led_cdev, devno, 1);if (retvalue 0){pr_err(cdev_add failed!\r\n);goto err_cdev_add;}/* 10、创建设备类 */led_class class_create(THIS_MODULE, LED_NAME);if (IS_ERR(led_class)){pr_err(create class failed!\r\n);retvalue PTR_ERR(led_class);goto err_class_create;}/* 11、创建设备节点 */led_device device_create(led_class, NULL, devno, NULL, LED_NAME);if (IS_ERR(led_device)){pr_err(create device failed!\r\n);retvalue PTR_ERR(led_device);goto err_device_create;}pr_info(LED driver initialized\n);return 0;err_device_create:class_destroy(led_class); err_class_create:cdev_del(led_cdev); err_cdev_add:unregister_chrdev_region(devno, 1); err_alloc_chrdev:iounmap(IMX6U_CCM_CCGR1);iounmap(SW_MUX_GPIO1_IO03);iounmap(SW_PAD_GPIO1_IO03);iounmap(GPIO1_DR);iounmap(GPIO1_GDIR); err_ioremap:return retvalue; }/** description : 驱动出口函数* param : 无* return : 无*/ static void __exit led_exit(void) {/* 销毁设备节点 */device_destroy(led_class, devno);/* 销毁设备类 */class_destroy(led_class);/* 删除 cdev */cdev_del(led_cdev);/* 释放设备号 */unregister_chrdev_region(devno, 1);/* 取消映射 */iounmap(IMX6U_CCM_CCGR1);iounmap(SW_MUX_GPIO1_IO03);iounmap(SW_PAD_GPIO1_IO03);iounmap(GPIO1_DR);iounmap(GPIO1_GDIR);pr_info(LED driver exited\n); }module_init(led_init);module_exit(led_exit);MODULE_LICENSE(GPL); MODULE_AUTHOR(fairy); MODULE_DESCRIPTION(LED Driver);使用udev后仅仅使用insmod加载驱动就可以自动创建设备文件使用ledApp测试时实验结果如预期         卸载只需要使用rmmod不必在手动使用rm删除设备节点 2动态加载_新方式         继续翻阅文档发现下一节新字符设备驱动实验的观点与AI不谋而合         不过对于设备号的分配却不相同文档是先静态后动态而AI是先动态再静态。重新问了几次它自己推翻了自己问及原因时它这样答道         那么就遵循现代Linux驱动开发的推荐做法使用动态分配。         同时文档里使用了“设置文件私有数据”这种做法在现代 Linux 驱动开发中也是非常常见且推荐的因为它可以方便地在驱动的其他操作函数如 read、write、release 等中访问设备相关的数据。         优化后的代码如下 #include linux/types.h #include linux/kernel.h #include linux/delay.h #include linux/ide.h #include linux/init.h #include linux/module.h #include linux/errno.h #include linux/gpio.h #include linux/cdev.h #include linux/device.h #include linux/io.h #include linux/slab.h #include linux/uaccess.h#define LED_NAME led /* 设备名字 */#define LEDOFF 0 /* 关灯 */ #define LEDON 1 /* 开灯 *//* 寄存器物理地址 */ #define CCM_CCGR1_BASE (0X020C406C) #define SW_MUX_GPIO1_IO03_BASE (0X020E0068) #define SW_PAD_GPIO1_IO03_BASE (0X020E02F4) #define GPIO1_DR_BASE (0X0209C000) #define GPIO1_GDIR_BASE (0X0209C004)/* 设备结构体 */ struct led_dev {dev_t devno; /* 设备号 */struct cdev cdev; /* 字符设备 */struct class *class; /* 设备类 */struct device *device; /* 设备实例 */void __iomem *reg_base; /* 寄存器基地址 */int led_state; /* LED 状态 */ };static struct led_dev *led_devices; /* 设备实例 *//** description : LED打开/关闭* param - sta : LEDON(0) 打开LEDLEDOFF(1) 关闭LED* param - reg_base: 寄存器基地址* return : 无*/ static void led_switch(u8 sta, void __iomem *reg_base) {u32 val;if (sta LEDON){val readl(reg_base);val ~(1 3);writel(val, reg_base);} else if (sta LEDOFF){val readl(reg_base);val | (1 3);writel(val, reg_base);} }/** description : 打开设备* param - inode : 传递给驱动的inode* param - filp : 设备文件file结构体有个叫做private_data的成员变量* 一般在open的时候将private_data指向设备结构体。* return : 0 成功;其他 失败*/ static int led_open(struct inode *inode, struct file *filp) {struct led_dev *dev;/* 获取设备结构体 */dev container_of(inode-i_cdev, struct led_dev, cdev);filp-private_data dev; /* 设置私有数据 */pr_info(Device opened\n);return 0; }/** description : 从设备读取数据* param - filp : 要打开的设备文件(文件描述符)* param - buf : 返回给用户空间的数据缓冲区* param - cnt : 要读取的数据长度* param - offt : 相对于文件首地址的偏移* return : 读取的字节数如果为负值表示读取失败*/ static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt) {return 0; }/** description : 向设备写数据* param - filp : 设备文件表示打开的文件描述符* param - buf : 要写给设备写入的数据* param - cnt : 要写入的数据长度* param - offt : 相对于文件首地址的偏移* return : 写入的字节数如果为负值表示写入失败*/ static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt) {struct led_dev *dev filp-private_data;unsigned char databuf[1];int retvalue copy_from_user(databuf, buf, cnt);if (retvalue 0){pr_err(kernel write failed!\r\n);return -EFAULT;}/* 使用设备私有数据 */led_switch(databuf[0], dev-reg_base);return 0; }/** description : 关闭/释放设备* param - filp : 要关闭的设备文件(文件描述符)* return : 0 成功;其他 失败*/ static int led_release(struct inode *inode, struct file *filp) {pr_info(Device released\n);return 0; }/* 设备操作函数 */ static struct file_operations led_fops {.owner THIS_MODULE,.open led_open,.read led_read,.write led_write,.release led_release, };/** description : 驱动入口函数* param : 无* return : 无*/ static int __init led_init(void) {u32 val;int retvalue;/* 动态分配设备结构体 */led_devices kzalloc(sizeof(struct led_dev), GFP_KERNEL);if (!led_devices){pr_err(Failed to allocate device data\n);return -ENOMEM;}/* 1、寄存器地址映射 */led_devices-reg_base ioremap(GPIO1_DR_BASE, 4);if (!led_devices-reg_base){pr_err(ioremap failed!\r\n);retvalue -ENOMEM;goto err_ioremap;}/* 2、使能GPIO1时钟 */val readl(ioremap(CCM_CCGR1_BASE, 4));val ~(3 26); /* 清除以前的设置 */val | (3 26); /* 设置新值 */writel(val, ioremap(CCM_CCGR1_BASE, 4));/* 3、设置GPIO1_IO03的复用功能 */writel(5, ioremap(SW_MUX_GPIO1_IO03_BASE, 4));/* 4、设置GPIO1_IO03的IO属性 */writel(0x10B0, ioremap(SW_PAD_GPIO1_IO03_BASE, 4));/* 5、设置GPIO1_IO03为输出功能 */val readl(ioremap(GPIO1_GDIR_BASE, 4));val ~(1 3); /* 清除以前的设置 */val | (1 3); /* 设置为输出 */writel(val, ioremap(GPIO1_GDIR_BASE, 4));/* 6、默认关闭LED */val readl(led_devices-reg_base);val | (1 3);writel(val, led_devices-reg_base);/* 7、动态分配设备号 */retvalue alloc_chrdev_region(led_devices-devno, 0, 1, LED_NAME);if (retvalue 0){pr_err(Failed to allocate device number\n);goto err_alloc_chrdev;}/* 8、初始化 cdev */cdev_init(led_devices-cdev, led_fops);led_devices-cdev.owner THIS_MODULE;/* 9、添加 cdev 到内核 */retvalue cdev_add(led_devices-cdev, led_devices-devno, 1);if (retvalue 0){pr_err(cdev_add failed!\r\n);goto err_cdev_add;}/* 10、创建设备类 */led_devices-class class_create(THIS_MODULE, LED_NAME);if (IS_ERR(led_devices-class)){pr_err(create class failed!\r\n);retvalue PTR_ERR(led_devices-class);goto err_class_create;}/* 11、创建设备节点 */led_devices-device device_create(led_devices-class, NULL, led_devices-devno, NULL, LED_NAME);if (IS_ERR(led_devices-device)){pr_err(create device failed!\r\n);retvalue PTR_ERR(led_devices-device);goto err_device_create;}pr_info(LED driver initialized\n);return 0;err_device_create:class_destroy(led_devices-class); err_class_create:cdev_del(led_devices-cdev); err_cdev_add:unregister_chrdev_region(led_devices-devno, 1); err_alloc_chrdev:iounmap(led_devices-reg_base); err_ioremap:kfree(led_devices);return retvalue; }/** description : 驱动出口函数* param : 无* return : 无*/ static void __exit led_exit(void) {/* 销毁设备节点 */device_destroy(led_devices-class, led_devices-devno);/* 销毁设备类 */class_destroy(led_devices-class);/* 删除 cdev */cdev_del(led_devices-cdev);/* 释放设备号 */unregister_chrdev_region(led_devices-devno, 1);/* 取消映射 */iounmap(led_devices-reg_base);/* 释放设备结构体 */kfree(led_devices);pr_info(LED driver exited\n); }module_init(led_init);module_exit(led_exit);MODULE_LICENSE(GPL); MODULE_AUTHOR(fairy); MODULE_DESCRIPTION(LED Driver);实验结果如预期接下来可以使用设备树来尝试更新的方法 3设备树的初步了解         关于设备树的介绍先初步浏览了解一下知道设备树与驱动开发相互配合有函数可以访问设备树节点信息就行再通过后面的例子进行深入学习加深印象。         浏览了文档的后面内容驱动这一章节的篇幅是真的大如果一章一章地学实在不符合我想要快速上手的目的。后面的章节有SPI、I2C什么的这个还是等到需要用到的时候再专门查文档学习吧。         先尝试搭建Qt环境吧如果成功的话那么后续可以边开发驱动边开发对应的界面来实现复杂功能的控制 四、Qt环境搭建尝试         尝试了许久各种混账的兼容性问题频出最终发现还是不如考古。正点原子资料盘里的虚拟机光盘里已经搭建好了所有环境可以直接使用可以根据目录跳转到本章的第3个的第④个。前面的内容少儿不宜埋藏着笔者深深的怨气。 1、Qt安装_Windows可跳过         这里选用的是Qt5.15.2,这是Qt5的最后一个版本同时也是LTS。从这个镜像网站里下载Qt Downloads         安装时遵循一般博客里的做法即可首先是要创建账号的。不过直接进入这个程序下载还是会失败贼他宝贝的麻烦需要让Qt下载程序用镜像网站下载参考博客windows安装QT时出现“无法下载存档……”解决办法 - lmore - 博客园         注意腾讯的镜像网站里只有6.8以上的版本一共就三个不要用。清华的镜像可以下载5.12.2但下载6.8.1也会报什么文档下载失败的错误。试了几个镜像就清华的这个比较全但这些镜像下载旧版要比官方好但新版就不行了         在你Qt下载程序所在的目录打开终端输入下面命令左边是你的Qt下载程序输入前面./qt然后按Tab键一般就可以自动补全了。 .\qt-online-installer-windows-x64-4.8.1_2.exe --mirror https://mirrors.tuna.tsinghua.edu.cn/qt/         当选择版本时一开始是没有Qt5之类的版本把右边的Archive勾选上然后再点击筛选 2使用正点原子项目可跳过 ①转移项目         把资料盘里的Qt应用程序复制到一个不含中文和特殊符号的路径 ②编译项目         这里先测试一下这个Qt程序是什么样子的选择MinGW64bit这个编译工具链         找到刚才那个项目里的pro文件         出现这个界面后先勾选MinGW64bit下图为32bit都差不多然后向下滑动点击configure program         进入到下面项目         点击上方的构建栏里面有运行         点击运行后就可以编译出Qt应用程序了一切如预期那样。不过要注意此时编译的程序是x86_x64架构的后缀名为exe而非是开发板arm32架构后缀名为elf文件 3交叉编译 ①下载交叉编译工具链可跳过         在Downloads | 9.2-2019.12 – Arm Developer下载9.2的交叉编译工具链如果是Linux使用那么就下载下面这个         如果是Windows那么就下载这个                  下载后把它解压在一个合适的目录不能含有中文。         不过考虑到这个编译器暂时不会与其他编译器的名称起冲突那么就先添加环境变量。按下WinX选择【系统】再点击【高级系统设置】再点击【环境变量】。要编辑的是下面这个Path         在里面把刚才的路径复制过去下面是参考自行修改 E:\Tools\Develop\ToolsKits\ARM\arm-gnu-toolchain-14.2.rel1-mingw-w64-i686-arm-none-linux-gnueabihf\bin     一路点击确定最后重启电脑。重启后打开终端输入下面语句观察是否有版本信息 arm-none-linux-gnueabihf-gcc -v ②交叉编译Qt源码库_Windows失败的 下载5.12.2的Qt源码 Index of /archive/qt/5.15/5.15.2/single 下载后找到一个不含中文的目录解压 找到如下路径         由于我们的目标是编译arm32平台的linux程序所以这里选择linux-arm-gnueabi-g,用记事本打开可以看到这里的编译器与我们下载的编译器基本是匹配的而且前面也将环境变量添加上去了就不需要再这里添加路径了。         所以只需要把arm-linux前缀改为arm-none-linuxgnueabi改为gnueabihf即可                 在Qt源码目录打开终端输入下面命令 ./configure.bat -release -opensource -prefix E:\Tools\Develop\ToolsKits\Qt\qt-5.15.2-arm -xplatform linux-arm-gnueabi-g -nomake tests -nomake examples -no-opengl -skip qtvirtualkeyboard -skip qtwebengine -prefix指定Qt库的安装路径自行选择 -xplatform指定交叉编译平台 -nomake跳过不需要的模块以加快编译速度         如果出现下面错误在环境变量里添加MSVC的bin目录即可         这个nmake的路径比较复杂首先找到安装的VS的目录如下2022是版本号按此路径最终找到下面目录自行替换 C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.41.34120\bin\Hostx64\x64 C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.41.34120\include         不过运行这个bat脚本中可能会出现下面这个沙雕错误荼毒无穷添加环境变量也没有任何作用         根据VS安装目录找到下面路径在这里打开终端执行下面命令 .\vcvarsall.bat x64         然后你就会发现啥用没有。后续找了许久发现忘了制定平台 ./configure.bat -release -opensource -prefix E:\Tools\Develop\ToolsKits\Qt\qt-5.15.2-arm -xplatform linux-arm-gnueabi-g -nomake tests -nomake examples -no-opengl -skip qtvirtualkeyboard -skip qtwebengine -platform win32-g         但我他宝贝的没高兴多久又寄了        找到下面路径         用记事本打开qglobal.h ,添加这个头文件 #include limits         诸如此类尝试了许多种方法最终带着深深的怨气总算找到了疑似靠谱的方法实用MSVC。按Win打开菜单找到VS的命令行x64和x86随意这里用的是         x64 Native Tools Command Prompt for VS 2022         在打开的cmd窗口中使用cd命令跳转到Qt源码的目录不过要注意的是在cd命令后加上/d参数才能执行跨盘操作 cd /d E:/Tools/Develop/ToolsKits/Qt/qt-everywhere-src-5.15.2/         然后输入下面命令不用加platform选项 configure.bat -release -opensource -prefix E:\Tools\Develop\ToolsKits\Qt\qt-5.15.2-arm -xplatform linux-arm-gnueabi-g -nomake tests -nomake examples -no-opengl -skip qtvirtualkeyboard -skip qtwebengine -platform win32-g         输入y即可 如果出现下图说明qmake.tconf里的编译工具集的名称没有写对或者环境变量没有生效自行检查 然后使用make构建电脑有几核就输入几 mingw32-make -j16         然后就没有然后了会报一些C错误 ③交叉编译Qt源码库_Linux失败的          下载5.12.2的Qt源码选择下面那个tar.xz Index of /archive/qt/5.15/5.15.2/single         然后下载工具链Arm GNU Toolchain Downloads – Arm Developer         通过Filezila传输到虚拟机里         工具链的解压用下面命令Qt源码也是如此 tar -xvJf arm-gnu-toolchain-14.2.rel1-x86_64-arm-none-linux-gnueabihf.tar.xz         添加环境变量需要修改下面文件 sudo vim /etc/environment         在原变量里加上冒号后面再添加路径路径改为自己的工具链路径 PATH/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/home/fairy/Embedded/Toolkits/toolchain/arm-gnu-toolchain-14.2.rel1-x86_64-arm-none-linux-gnueabihf/bin         重启后环境变量就会生效。         接着我们安装依赖先更新apt sudo apt update         然后安装依赖 sudo apt install build-essential libgl1-mesa-dev libxkbcommon-dev libxcb-xinerama0-dev libxcb-xinput-dev libfontconfig1-dev libfreetype6-dev libdbus-1-dev libicu-dev libssl-dev libjpeg-dev libpng-dev libpcre3-dev libz-dev         由于配置过长需要配置一个脚本,路径/path/to/install/qt5.12.2自行替换 #!/bin/bash ./configure -prefix /path/to/install/qt5.12.2 \-opensource \-confirm-license \-release \-xplatform linux-arm-gnueabi-g \-no-opengl \-no-sse2 \-no-xcb \-qt-libjpeg \-qt-libpng \-qt-zlib \-qt-pcre \-qt-freetype \-qt-harfbuzz \-no-openssl \-no-cups \-no-dbus \-no-glib \-no-iconv \-no-icu \-no-eglfs \-no-linuxfb \-no-kms \-no-gtk \-no-xkbcommon \-no-xcb-xlib \-no-xinput2 \-no-xcb-xinput \-no-xcb-randr \-no-xcb-shape \-no-xcb-sync \-no-xcb-xfixes \-no-xcb-xkb \-no-xkbcommon-x11 \-no-xrender \-no-xi \-no-xext \-no-fontconfig \-no-freetype \-no-harfbuzz \-no-pcre \-no-zlib \-no-jpeg \-no-png \-no-gif \-no-sqlite \-no-libudev \-no-evdev \-no-mtdev \-no-tslib \-no-libinput \-no-gstreamer \-no-pulseaudio \-no-alsa \-no-vulkan \-no-qml-debug \-no-compile-examples \-nomake examples \-nomake tests \ -no-tslib         使用chmod赋予脚本权限,假设脚本为autoConfig.sh chmod 777 autoConfig.sh         然后在源码目录里运行脚本执行脚本之后就会报缺少limits什么的错误到下面目录找到qglobal.h 添加这个头文件即可 #include limits 构建成功后 然后使用make make -j16 ④交叉编译_正点原子         事实证明有些护城河就不是河简直就是天堑。知道C的abi不稳定但没想到会是这般不稳定尝试了一天在已有体系上5.12实在编译不了我都快准备放弃Qt使用LVGL了。最后只能强忍着不适继续尝试下去。         可能是MinGW版本不对可能是GCC版本不对可能是Qt配置的某些选项不对可能是不同厂商的gcc对某些特定abi不兼容可能是Ubuntu版本不对……可能性太多了Windows平台是不寄予希望了还是考古吧已老实求放过。         按照文档指示安装正点原子的ubuntu2016 在原有网卡基础上再添加一个NAT模式用于联网         设置好之后打开虚拟机进入设置找到NetWork         进入NetWork后可以看到两个Wired有线连接第一个往往是桥接模式eth0第二个是NAT模式eth1。为了与外界进行交互我们修改第一个桥接模式的ip把它设置为静态IP。         点击Options找到IPv4 Settings然后切换为手动模式静态         使用ifconfig查看ip已被正确设置         记住下面这个ens37这个ip这是NAT模式的我们使用Filezila与虚拟机传输用的ip就是它。此外正点原子已经为这个Ubuntu安装好了FTP服务并且已经配置好了。         如果出现乱码在站点管理器里把字符集设置为强制使用UTF-8         编译一二十分钟然后报错这一点我是万万没想到的。报了一个override错误         又重试了一遍终于成功了。使用的脚本是正点原子里的。此步骤可以省略因为正点原子虚拟光盘里已经有编译好的Kits ./configure -prefix /home/alientek/Qt/arm-qt \ -opensource \ -confirm-license \ -release \ -strip \ -shared \ -xplatform linux-arm-gnueabi-g \ -optimized-qmake \ -cstd c11 \ --rpathno \ -pch \ -skip qt3d \ -skip qtactiveqt \ -skip qtandroidextras \ -skip qtcanvas3d \ -skip qtconnectivity \ -skip qtdatavis3d \ -skip qtdoc \ -skip qtgamepad \ -skip qtlocation \ -skip qtmacextras \ -skip qtnetworkauth \ -skip qtpurchasing \ -skip qtremoteobjects \ -skip qtscript \ -skip qtscxml \ -skip qtsensors \ -skip qtspeech \ -skip qtsvg \ -skip qttools \ -skip qttranslations \ -skip qtwayland \ -skip qtwebengine \ -skip qtwebview \ -skip qtwinextras \ -skip qtx11extras \ -skip qtxmlpatterns \ -make libs \ -make examples \ -nomake tools -nomake tests \ -gui \ -widgets \ -dbus-runtime \ --glibno \ --iconvno \ --pcreqt \ --zlibqt \ -no-openssl \ --freetypeqt \ --harfbuzzqt \ -no-opengl \ -linuxfb \ --xcbno \ -tslib \ --libpngqt \ --libjpegqt \ --sqliteqt \ -plugin-sql-sqlite \ -I/home/alientek/tslib-1.21/arm-tslib/include \ -L/home/alientek/tslib-1.21/arm-tslib/lib \ -recheck-all time (make -j16)         简直就是神迹! time (make install) 后续又试了一下同样编译器和构建命令的情况下Ubuntu2024会出现下面错误         后续测试了一下Ubunt2024、Ubuntu2016与gcc9.2、gcc14.2的排列组合只有Ubuntu2016和gcc9.2的组合可以正常编译。         换成Windows平台使用同样的gcc编译器9.2同样的命令只会编译出下面结果。也许是MinGW版本不对MinGW32gcc9.2和MinGW64gcc14.2都不行 ./configure.bat -prefix E:\Tools\Develop\ToolsKits\Qt\qt-5.15.2-arm-gcc -opensource -confirm-license -release -strip -shared -xplatform linux-arm-gnueabi-g -optimized-qmake -cstd c11 --rpathno -pch -skip qt3d -skip qtactiveqt -skip qtandroidextras -skip qtcanvas3d -skip qtconnectivity -skip qtdatavis3d -skip qtdoc -skip qtgamepad -skip qtlocation -skip qtmacextras -skip qtnetworkauth -skip qtpurchasing -skip qtremoteobjects -skip qtscript -skip qtscxml -skip qtsensors -skip qtspeech -skip qtsvg -skip qttools -skip qttranslations -skip qtwayland -skip qtwebengine -skip qtwebview -skip qtwinextras -skip qtx11extras -skip qtxmlpatterns -make libs -make examples -nomake tools -nomake tests -gui -widgets -dbus-runtime --glibno --iconvno --pcreqt --zlibqt -no-openssl --freetypeqt --harfbuzzqt -no-opengl -linuxfb --xcbno --libpngqt --libjpegqt --sqliteqt -plugin-sql-sqlite -recheck-all -platform win32-g         MinGW64 8.1.0会报满屏的缺少定义的错误 ⑤添加编译工具链         这些乱七八糟的的构建体系给人一种“生命总会找到出路甭管路子有多野”的恶感linux或者说Qt、C的护城河远比想象的牢固。         在虚拟机里安装Linux下的Qt网址为Index of /official_releases/online_installers与Windows下的安装基本一致         这个还是遵循正点原子文档使用这个命令在Ubuntu2016里下载在Ubuntu2024里使用编译好的Qt模块会提示缺少positioning模块         好吧正点原子这个光盘里什么都有不用安装。 wget http://download.qt.io/archive/qt/5.12/5.12.9/qt-opensource-linux-x64-5.12.9.run chmod ux qt-unified-linux-x64-online.run sudo ./qt-unified-linux-x64-online.run --mirror https://mirrors.tuna.tsinghua.edu.cn/qt/         安装的过程与正点原子相同目录就默认在/opt/Qt, 安装过程可能出现下面提示         安装这个库即可不然无法打开程序 sudo apt install libxcb-cursor0 libxcb-cursor-dev         安装完成后可以输入下面命令来打开或者直接到该目录下运行sh脚本 /opt/Qt/Tools/QtCreator/bin/qtcreator.sh         点击左上角这个图表 输入一个Q即可看到安装好的Qt不过这里最好不要使用这个图标直接运行程序可以使用下面这个命令来运行避免后面诸多环境变量不一致 /opt/Qt5.12.9/Tools/QtCreator/bin/qtcreator.sh         把QtDesktop工程传输到虚拟机里打开前删除.pro.user这个文件         使用Qt打开选择第一个KitsATK-I.MX6U和Desktop那个Kits用于生成桌面应用程序观察效果然后点击配置工程。这里没用之前编译的Qt库是有原因的因为使用的时候又他宝贝的寄了。我只好非常可耻地使用正点原子编译好的Qt库不折腾了        点击左侧的Projects再次进入配置Kits的界面左侧已经有两个Kits点击哪个Kits的Build或者Run该Kits的名称就会加粗表示该工程使用这个Kits。         回到工程里左下角也可以选择哪个Kits         选择桌面那个Kits然后去点击运行可以生成下图桌面程序        只能说还是厂商的靠谱 虽说版本旧了但至少能用不会有那么多奇奇怪怪的问题 4远程调试 ①构建arm-linux程序         选择另一套kits然后构建不要运行构建完成后会在工程同级目录下有一个build目录里面有arm架构的elf程序 ②连接开发板         连接开发板的串口输入下面命令确保开发板的根文件系统版本大于v1.9。或者直接输入rysnc如果出现一堆提示那么就说明有rsync命令 cat /etc/version         回到虚拟机在Tools栏里找到最后一个选项点击设备。这里是默认设置好的把里面的Host的IP改为实际开发板里的记得在开发板里输入ifconfig确定ip点击OK         然后点击Kits可以看到这里已经配置好了rsync         在Projects下点击刚才的rsync套件 再点击里面的run由于这个工程名为Desktop会与开发板里的Desktop程序起冲突         所以需要勾选上面的复选框左面的路径可以到Build里去查找 添加一个SSH命令图里多打了一个空格 -p %{Device:SshPort} %{Device:UserName}%{Device:HostAddress} mkdir -p %{CurrentRun:Executable:Path} 再添加一个scp传输程序用的 -P %{Device:SshPort} %{CurrentRun:Executable:FileName} %{Device:UserName}%{Device:HostAddress}:%{CurrentRun:Executable:FilePath}         下面这个路径也要改 再添加一个设备就是刚才Remote Diretory下的程序要勾选那个复选框 /opt/test/bin/QDesktop         回到Edit界面点击运行除了下方有一些红字外一切正常 这个桌面程序是要比开发板自带的画质要低一些         在开发板的串口输入top命令可以看到有两个QDesktop在运行难怪刚才那么卡图片还会一闪一闪的应该是部署时没有正确沙掉进程         在这个界面下输入k命令后面跟着PID就可以沙雕对应进程了这里保留时间短的刚才烧录的程序。输入q可以退出         不用担心开发板里的程序开发板重启后会自动运行自带的桌面         可以看到虚拟机里的这个桌面左下角是没有图片的         基本的Qt环境已经搭建好了下一步开发Qt时会轻松不少最主要的是能看到最终要实现的效果近在眼前。不过在虚拟机里开发着实不方便后续准备尝试把开发界面的任务迁移至主机平台使用CLion配合Qt Design什么的开发部署调试再放到虚拟机里。         这个方案之所以可行还是因为Qt强大的跨平台不同平台相同接口。只不过不同的平台需要不同的库而这个Qt库的编译是相当折磨与java的“一次编写到处运行”完全不一样。         之前还觉得LVGL使用纯C语编写开发界面很麻烦现在看来真的是很棒的设计不会有那么多烦人的兼容性问题而且界面的开发完全可以使用C等来封装一些基本的lvgl接口达到类“Qt”的那种开发效果。或者用别的语言来调用C编译的库总之移植起来相当方便。 五、Linux驱动开发         有设备树的驱动开发才算完整的Linux驱动开发嘛 1设备树下的LED驱动  ①初识设备树         每个节点无论是根节点还是子节点都是用一个花括号包起来。花括号中上面是属性下面是子节点也可能没有。这种写法很递归也有点像C的类上面是“成员变量”下面是“成员函数定义”属性部分有些像标签语言。反正怎么好理解就怎么记不讨论先有儿子还是先有爸爸的问题 以此类推就不水了 后面我们就以LED设备为例来讲解,下面我有这些问题 1compatible怎么用于匹配名称是根据前面已经出现的还是我自己随便起还是说有固定的规则 2这里面默认触发模式是什么有哪些模式各个模式有什么用 3gpios属性被定义为gpio1 5 GPIO_ACTIVE_HIGH那么gpio1是不是需要已经出现过的引脚 4我想要添加新设备是不是可以自己再创建一个dts然后使用include包含前面的dts文件然后就可以在这个新文件里使用根节点追加的方式 5我暂时想不到什么问题了你就以一名初学者的角度来帮我想想还有哪些问题值得问然后解答它 ②设备树编写         结合正点原子示例代码和文档准备新建一个dts文件比如mx6ull-alientek-emmc.dts在里面引用前面的那个完备的dts #include imx6ull-14x14-emmc-7-1024x600-c.dts 依次往上找到被包含的dtsi文件ixm6ull-14×14-evk.dtsi我们可以在一个dts文件里找到pinctrl里的gpio-leds535行左右这是属于iomuxc节点的 往上我们可以看到leds所在节点107行左右这些都是写好的。leds节点中没有state属性那么默认就是okay启用         也就是说如果前面的dts没有定义这些节点我们可以通过类似于下面这种方式来追加相关内容这是一般的开发步骤。但evk板既然给了那就不写了吧因为我们知道它是怎么来的 根据文档使用pinctrl后还需要检查引脚是否冲突文档中特别提到阿尔法板是没有用到tsc这个接口的我们需要把它注释掉 通过搜索功能可以看到在650行附近有tsc的定义我们注释掉即可         搜索gpio 3可以看到外设节点tsc里也会用到GPIO1的3号引脚这里的状态是disabled所以不会冲突。也注释掉比较阿尔法板并没有用到这个接口 可以看到上面三个dts是层层嵌套的左边依赖且只依赖一个右边 为了方便测试我们可以让新建的dts只包含imx6ull-14×14-evk把左边两个定义的节点复制过来 #include imx6ull-14x14-evk.dtsusdhc2 {pinctrl-names default, state_100mhz, state_200mhz;pinctrl-0 pinctrl_usdhc2_8bit;pinctrl-1 pinctrl_usdhc2_8bit_100mhz;pinctrl-2 pinctrl_usdhc2_8bit_200mhz;bus-width 8;non-removable;status okay; };i2c2 {goodix_ts5d {reg 0x5d;}; };lcdif {display0: display {bits-per-pixel 16;bus-width 24;display-timings {native-mode timing0;timing0: timing0 {clock-frequency 51000000;hactive 1024;vactive 600;hfront-porch 160;hback-porch 140;hsync-len 20;vback-porch 20;vfront-porch 12;vsync-len 3;hsync-active 0;vsync-active 0;de-active 1;pixelclk-active 0;};};}; }; 找到dts目录下的Makefile文件看看是否有新建的dts文件注意这里添加的是dtb如果没有就找到对应位置添加 一切就绪后使用下面命令来编译dtb make dtbs ③驱动编写         把原先的led工程复制一份打开后开始编写驱动。这里先定义一个设备结构体 /* 设备结构体 */ struct led_dev {dev_t devno; /* 设备号 */struct cdev cdev; /* 字符设备 */struct class *class; /* 设备类 */struct device *device; /* 设备实例 */int led_gpio; /* GPIO 引脚 */int led_state; /* LED 状态 */ };         在init函数里分配这个设备结构体 /* 动态分配设备结构体 */led_devices kzalloc(sizeof(struct led_dev), GFP_KERNEL);if (!led_devices){pr_err(Failed to allocate device data\n);return -ENOMEM;} 从这里可以看出led1是在/leds/led1这个路径上 那么就可以使用of_*函数来获取对应的设备树节点 struct device_node *np;/* 从设备树中获取LED的GPIO */np of_find_node_by_path(/leds);if (!np){pr_err(Failed to find LED node in device tree\n);retvalue -ENODEV;goto err_find_node;} 接着获取LED的引脚 /* 获取 LED GPIO 引脚 */led_device-led_gpio of_get_named_gpio(np, led1, 0);if (led_device-led_gpio 0) {pr_err(Failed to get LED GPIO\n);ret led_device-led_gpio;goto err_get_gpio;} 申请GPIO这里的ret变量只是获取返回值状态 /* 申请 GPIO */ret gpio_request(led_device-led_gpio, led1);if (ret) {pr_err(Failed to request LED GPIO\n);goto err_gpio_request;} 调用GPIO函数来设置GPIO状态根据设备树可以知道它是低电平有效那么高电平就是关闭LED /* 设置 GPIO 方向 */gpio_direction_output(led_device-led_gpio, 1); // 默认关闭 LED 可以通过下面函数来设置GPIO的引脚值其余与之前无异 gpio_set_value(dev-led_gpio, 0); 完整代码为 #include linux/module.h #include linux/init.h #include linux/kernel.h #include linux/fs.h #include linux/cdev.h #include linux/device.h #include linux/slab.h #include linux/uaccess.h #include linux/of.h #include linux/of_gpio.h #include linux/gpio.h#define DEVICE_NAME led // 设备名称/* 设备结构体 */ struct led_dev {dev_t devno; // 设备号struct cdev cdev; // 字符设备struct class *class; // 设备类struct device *device; // 设备实例int led_gpio; // LED GPIO 引脚 };static struct led_dev *led_device; // 设备实例/** description : 打开设备*/ static int led_open(struct inode *inode, struct file *filp) {struct led_dev *dev container_of(inode-i_cdev, struct led_dev, cdev);filp-private_data dev; // 设置私有数据pr_info(Device opened\n);return 0; }/** description : 从设备读取数据*/ static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt) {return 0; }/** description : 向设备写数据*/ static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt) {struct led_dev *dev filp-private_data;unsigned char databuf[1];int ret;ret copy_from_user(databuf, buf, cnt);if (ret 0) {pr_err(Failed to copy data from user\n);return -EFAULT;}/* 控制 LED */if (databuf[0] 1) {gpio_set_value(dev-led_gpio, 0); // 点亮 LED} else if (databuf[0] 0) {gpio_set_value(dev-led_gpio, 1); // 关闭 LED}return 0; }/** description : 关闭设备*/ static int led_release(struct inode *inode, struct file *filp) {pr_info(Device released\n);return 0; }/* 设备操作函数 */ static struct file_operations led_fops {.owner THIS_MODULE,.open led_open,.read led_read,.write led_write,.release led_release, };/** description : 驱动入口函数*/ static int __init led_init(void) {int ret;struct device_node *np;/* 动态分配设备结构体 */led_device kzalloc(sizeof(struct led_dev), GFP_KERNEL);if (!led_device) {pr_err(Failed to allocate device data\n);return -ENOMEM;}/* 查找设备树节点 */np of_find_node_by_path(/leds/led1);if (!np) {pr_err(Failed to find LED node in device tree\n);ret -ENODEV;goto err_find_node;}/* 获取 LED GPIO 引脚 */led_device-led_gpio of_get_named_gpio(np, gpios, 0);if (led_device-led_gpio 0){pr_err(Failed to get LED GPIO\n);ret led_device-led_gpio;goto err_get_gpio;}/* 申请 GPIO */ret gpio_request(led_device-led_gpio, my-led);if (ret){pr_err(Failed to request LED GPIO\n);goto err_gpio_request;}/* 设置 GPIO 方向 */gpio_direction_output(led_device-led_gpio, 1); // 默认关闭 LED/* 动态分配设备号 */ret alloc_chrdev_region(led_device-devno, 0, 1, DEVICE_NAME);if (ret 0) {pr_err(Failed to allocate device number\n);goto err_alloc_chrdev;}/* 初始化 cdev */cdev_init(led_device-cdev, led_fops);led_device-cdev.owner THIS_MODULE;/* 添加 cdev 到内核 */ret cdev_add(led_device-cdev, led_device-devno, 1);if (ret 0) {pr_err(Failed to add cdev\n);goto err_cdev_add;}/* 创建设备类 */led_device-class class_create(THIS_MODULE, DEVICE_NAME);if (IS_ERR(led_device-class)) {pr_err(Failed to create class\n);ret PTR_ERR(led_device-class);goto err_class_create;}/* 创建设备节点 */led_device-device device_create(led_device-class, NULL, led_device-devno, NULL, DEVICE_NAME);if (IS_ERR(led_device-device)) {pr_err(Failed to create device\n);ret PTR_ERR(led_device-device);goto err_device_create;}pr_info(LED driver initialized\n);return 0;err_device_create:class_destroy(led_device-class); err_class_create:cdev_del(led_device-cdev); err_cdev_add:unregister_chrdev_region(led_device-devno, 1); err_alloc_chrdev:gpio_free(led_device-led_gpio); err_gpio_request: err_get_gpio: err_find_node:kfree(led_device);return ret; }/** description : 驱动出口函数*/ static void __exit led_exit(void) {/* 销毁设备节点 */device_destroy(led_device-class, led_device-devno);/* 销毁设备类 */class_destroy(led_device-class);/* 删除 cdev */cdev_del(led_device-cdev);/* 释放设备号 */unregister_chrdev_region(led_device-devno, 1);/* 释放 GPIO */gpio_free(led_device-led_gpio);/* 释放设备结构体 */kfree(led_device);pr_info(LED driver exited\n); }module_init(led_init); module_exit(led_exit);MODULE_LICENSE(GPL); MODULE_AUTHOR(Your Name); MODULE_DESCRIPTION(LED Driver); ④驱动测试         由于我们没有对设备树的节点做任何修改在不更换前面编译后的dtb的情况下理应实现相同的效果。如果重新把设备树拷贝到SD卡的第一个分区里记得在Uboot启动时把bootcmd也改了因为两个设备树文件名称不同。         这里通过NFS挂载把编译后的驱动模块传输到开发板上 /etc/init.d/networking restart mount -t nfs -o nfsvers3 192.168.1.128:/home/fairy/nfs_share /mnt/nfs 如果在设备树里就把default-trigger设置为none那么就不需要通过下面这个设置了否则灯还会闪 echo none /sys/class/leds/sys-led/trigger         加载模块时可能会出现申请失败的问题把申请GPIO的代码给注释就行了原因在下面有。         可以通过下面命令来查看GPIO3LED的GPIO引脚是否被占用 cat /sys/kernel/debug/gpio 如前面提到LED的引脚莫名其妙被占用就是gpio-3旁边被一个“?”占用         后来试了一下只要在设备树里定义了这个引脚那么它就显示被“?”占用实际上谁也没占用它。查了一下发现内核会在启动时自动解析该节点并调用 gpio_request 申请 gpio1_io03也就是如果在设备树中已经定义了某个 GPIO 引脚那么内核会优先占用该引脚以便我们可以在用户空间中访问比如前面通过下面命令改变LED的触发状态 echo none /sys/class/leds/sys-led/trigger         比如亮灭 echo 1 /sys/class/leds/sys-led/brightness # 点亮 LED echo 0 /sys/class/leds/sys-led/brightness # 关闭 LED         这其实是“compatible gpio-leds;”所致意思是交由内核管理可以换成别的名称比如compatible atkalpha-gpioled; 2梳理         有第一个使用设备树开发linux驱动的经验后学习linux驱动的脉络清晰了一些希望其他的多少也能照葫芦画瓢。         下一步可以尝试在正点原子创建的桌面上添加一个按钮用于控制LED或蜂鸣器试一下GUI与驱动的交互。等GUI与驱动开发都熟练时或许可以更进一步学习linux内核、构建根文件系统等尝试使用imx6ull官方最新适配的linux或者Android以及配套的uboot和根文件系统再尝试Qt6.8.1         嵌入式Linux入门难各种环境搭建、兼容性问题就拦了一部分人。想精进也难各种工具、命令、规则、协议栈、C语言的奇思妙法等想想头都大。         希望大家都能有所收获
http://www.dnsts.com.cn/news/132110.html

相关文章:

  • 长沙 公司网站个人做网站用哪个主机好
  • 电子商务网站开发的过程东莞人才服务中心官网
  • 中国建设银行网站的主要功能大连网络推广营销
  • 安徽建站管理系统开发请描述网站开发的一般流程图
  • 平顶山城市住房城乡建筑网站asp网站检查
  • 英文网站建设注意什么网站建设做什么会计分录
  • WordPress P站和目网站
  • 公司做的网站列入什么科目赣州人才网官网招聘信息
  • 网站收录后然后怎么做上海网站开发外包公司
  • 做旅游网站的引言腾讯邮箱企业邮箱注册
  • 滕州市 网站建设公司网站建设产品
  • 低代码建站网页升级访问更新中
  • 昆山移动网站建设网站架构设计师月薪多少
  • 六色网站中国空间站最新消息新闻
  • 做微商去哪个网站推广社区电商平台排行榜
  • 做一个静态网站要多少钱夺宝网站建设
  • 南宁东凯做网站的公司wordpress添加悬浮按钮
  • 福建网站建建设房屋装修效果图用什么软件
  • 门户网站如何做seo如何将模板导入wordpress
  • 天台县网站建设哪家好游戏网站哪个好
  • 微网站建设哪家好网站开发qq群
  • 深圳网络开发公司有哪些做搜狗手机网站优化软
  • 帮忙做快站旅游网站辽宁建设工程信息网ca锁激活
  • 电子商务网站的设计与实现Asp网站开发入门
  • 网站的上传与发布wordpress android开源
  • 最牛视频网站建设做网站的热门行业
  • 运城手机网站制作普陀营销型网站建设
  • 网站建设和教学设计模板
  • 网站左侧导航代码手机网站字体大小规范
  • 网络营销策划书 范例seosem有什么区别