【IMX6ULL驱动开发学习】05.IMX6ULL驱动开发_编写第一个hello驱动【熬夜肝】

这篇具有很好参考价值的文章主要介绍了【IMX6ULL驱动开发学习】05.IMX6ULL驱动开发_编写第一个hello驱动【熬夜肝】。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

经过以下四个步骤,终于可以开始驱动开发了

01.安装交叉编译环境【附下载地址】
02.IMX6ULL烧写Linux系统
03.设置IMX6ULL开发板与虚拟机在同一网段
04.IMX6ULL开发板与虚拟机互传文件

目录

一、获取内核、编译内核
二、创建vscode工作区,添加内核目录和个人目录
三、了解驱动程序编写流程
四、第一个驱动程序 - hello驱动
五、IMX6ULL验证hello驱动

一、获取内核、编译内核

1、获取内核文件

获取Linux内核文件,可以从Linux Kernel官网下载,我这里为了跟开发板中的系统一致,避免出现其他问题,所以使用的韦东山老师提供的Linux-4.9.88内核文件,需要自取

链接:https://pan.baidu.com/s/111M2FsgJXAPsQ3ppeVwbFQ
提取码:p7wp

2、编译内核文件
为什么要编译内核文件,因为驱动代码的编译要基于编译好的内核文件的
在编译之前,要在~/.bashrc文件下添加两行内容,来指定编译的平台和工具链

export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabihf-

编译内核步骤:(如果中途报错了,自行百度一下就行,网上很多解决办法的,这里就不一一列举了)

  1. 先删除之前编译所生成的文件和配置文件,备份文件等
make mrproper

成功现象:无内容输出

  1. 设置内核的相关配置
make 100ask_imx6ull_defconfig

成功现象:

HOSTCC scripts/basic/fixdepHOSTCC scripts/kconfig/conf.o
SHIPPED scripts/kconfig/zconf.tab.c
SHIPPED scripts/kconfig/zconf.lex.c
SHIPPED scripts/kconfig/zconf.hash.c
HOSTCCscripts /kconfig/zconf.tab.o
HOSTLDscripts/kconfig/conf
#
#configuration written to .config
#
  1. 生成镜像文件(-j4会快一些,代表使用4个核心一起编译,具体用几个核心根据自己虚拟机具体情况而定)
make zImage -j4

成功现象:在 内核文件/arch/arm/boot/目录下 生成 zImage 文件,且没有报错

  1. 生成设备树(这一步也做一下,很快的,虽然我们用不到,就跟着步骤来嘛)
make dtbs

成功现象:输出几行内容,无报错

  1. 在家目录下新建 nfs_rootfs 目录,将镜像文件、生成的设备树拷贝其目录下 (可不做)
cp arch/arm/boot/zImage ~/nfs_rootfs
cp arch/arm/boot/dts/100ask_imx6ull-14x14.dtb ~/nfs_rootfs
  1. 编译内核模块
make modules

成功现象:输出很多.o文件,最后输出一些.ko文件,无报错


二、配置vscode,添加内核目录和个人目录

  1. 使用vscode打开linux-4.9.88内核目录,点击左上角 “文件” ,“将工作区另存为”,选择家目录下,随便起个名字
  2. 使用vscode打开自己要编写驱动的文件目录,右键自己的文件夹,选择“ 将文件夹添加到工作区”,选择自己创建的那个工作区
  3. 至此就可以在vscode同一个工作区下看到自己的驱动文件和linux内核文件了,方便后续独照内核文件内容

【IMX6ULL驱动开发学习】05.IMX6ULL驱动开发_编写第一个hello驱动【熬夜肝】,Linux驱动开发【IMX6ULL】,驱动开发,linux驱动,第一个驱动程序,IMX6ULL,insmod

  1. 在.vscode 下的c_cpp_properties.json 文件中设置工作路径
"/home/me/Linux-4.9.88/tools/virtio",
"/home/me/Linux-4.9.88/include/**",
"/home/me/Linux-4.9.88/include/linux/**",
"/home/me/Linux-4.9.88/arch/arm/include/**",
"/home/me/Linux-4.9.88/arch/arm/include/generated/**"

三、了解驱动程序编写流程

1. 先看内核目录下原有的驱动是怎么写的
打开Linux-4.9.88/drivers/char目录(看名字就猜到该目录下存放应该是字符驱动代码
发现有个 ds1602.c ,打开看看它是怎么写的(因为我学过了,选择这个文件,展示主要代码,有助于入门理解)

#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/capability.h>
#include <linux/init.h>
#include <linux/mutex.h>

#include <mach/hardware.h>
#include <asm/mach-types.h>
#include <asm/uaccess.h>
#include <asm/therm.h>

static DEFINE_MUTEX(ds1620_mutex);
static const char *fan_state[] = { "off", "on", "on (hardwired)" };

.....
..... 省略
.....

static int __init ds1620_init(void)
{
	int ret;
	struct therm th, th_start;

	if (!machine_is_netwinder())
		return -ENODEV;

	ds1620_out(THERM_RESET, 0, 0);
.....
..... 省略
.....

static int ds1620_open(struct inode *inode, struct file *file)
{
	return nonseekable_open(inode, file);
}

.....
..... 省略
.....

static ssize_t ds1620_read(struct file *file, char __user *buf, size_t count, loff_t *ptr)
{
	signed int cur_temp;
	signed char cur_temp_degF;

	cur_temp = cvt_9_to_int(ds1620_in(THERM_READ_TEMP, 9)) >> 1;

	/* convert to Fahrenheit, as per wdt.c */
	cur_temp_degF = (cur_temp * 9) / 5 + 32;

	if (copy_to_user(buf, &cur_temp_degF, 1))
		return -EFAULT;

	return 1;
}


.....
..... 省略
.....

static const struct file_operations ds1620_fops = {
	.owner		= THIS_MODULE,
	.open		= ds1620_open,
	.read		= ds1620_read,
	.unlocked_ioctl	= ds1620_unlocked_ioctl,
	.llseek		= no_llseek,
};

.....
..... 省略
.....

static void __exit ds1620_exit(void)
{
#ifdef THERM_USE_PROC
	remove_proc_entry("therm", NULL);
#endif
	misc_deregister(&ds1620_miscdev);
}

module_init(ds1620_init);
module_exit(ds1620_exit);

MODULE_LICENSE("GPL");

2. 驱动程序主要构成(主要是的前面四个)
· file_operations 结构体 : 为系统调用提供驱动程序入口的结构体
· module_init : 定义驱动模块的入口函数
· module_exit : 定义驱动模块的退出函数
· MODULE_LICENSE(“GPL”) : 声明模块许可证,指明这是GNU General Public License的任意版本,
                                                     否则在加载此模块时,会收到内核被污染 “kernel tainted” 的警告

· ds1620_init :ds1602初始化函数
· ds1620_open : 打开ds1602设备函数
· ds1620_read : 读ds1602设备函数
· ds1620_exit : ds1602退出驱动函数

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

3. 驱动程序实现过程及调用原理
· module_init 指定驱动入口函数
· 设备对象通过 struct file_operations 结构体定义
· 入口函数中调用 register_chrdev 函数,传入注册定义好的驱动设备变量,生成设备号
· 在开发板上通过 insmod 命令将编译好的驱动模块(.ko文件,要从虚拟机传到板子上哦)载入内核
· 通过应用程序可以对设备进行读写等操作,读写等操作通过系统调用 register_chrdev 结构体中指定的驱动模块读写等函数来实现
· close设备时,系统调用 module_exit 指定的驱动模块退出函数

以上便是驱动程序实现过程及应用程序调用驱动程序的原理(按照个人理解写的,大概是这么个流程,有不恰当的地方欢迎指出)

四、第一个驱动程序 - hello驱动

1. 在个人目录下新建 hello_drv.c 文件
2. 照葫芦画瓢,把上面 ds1602 用到的
头文件**都复制过来
3. 继续照葫芦画瓢编写 file_operations 结构体**

static const struct file_operations hello_drv = {
    .owner 		= THIS_MODULE,
	.read		= hello_read,
	.write		= hello_write,
	.open		= hello_open,
	.release    = hello_release,
};

假装我们的驱动也可以读、写,当然也必须有open和release(就是close)
当然 .owner = THIS_MODULE, 也必须有,原因见博客https://blog.csdn.net/a954423389/article/details/6101369

4. 研究file_operations结构体
每个函数都有对应的模板,可不是乱写的,因为这些函数组中都会被系统调用,参数都是固定的,可以按住ctrl键,用鼠标点击file_operations,跳转到该结构体定义处,可以看到每个函数指针的形式
【IMX6ULL驱动开发学习】05.IMX6ULL驱动开发_编写第一个hello驱动【熬夜肝】,Linux驱动开发【IMX6ULL】,驱动开发,linux驱动,第一个驱动程序,IMX6ULL,insmod

5. 照葫芦画瓢实现hello_read、hello_write、hello_open、hello_release函数

/*养成好习惯,驱动程序都加static修饰*/
static int hello_open (struct inode *node, struct file *filp)
{
	printk("hello_open\n");
	printk("%s %s %d\n",__FILE__, __FUNCTION__, __LINE__);
	return 0;
}

static ssize_t hello_read (struct file *filp, char *buf, size_t size, loff_t *offset)
{
	printk("hello_read\n");
	return size;  //返回读取字节数
}

static ssize_t hello_write (struct file *filp, const char *buf, size_t size, loff_t *offset)
{
	printk("hello_write\n");
	return size;  //返回写入字节数
}

static int hello_release (struct inode *node, struct file *filp)
{
	printk("hello_release\n");
	return 0;
}

6. 编写hello驱动的入口函数、出口函数
入口函数需要用到 register_chrdev 函数
出口函数(退出函数)需要用到 unregister_chrdev 函数,但是 ds1602.c 中没有这两个函数
没关系,我们在vscode中搜索 register_chrdev, 随便点一个看一下,研究一下用法(实在看不懂百度一下哈哈)
也可以用linux命令查找(在内核的drivers/char目录下查找)

grep "register_chrdev" * -nwr

【IMX6ULL驱动开发学习】05.IMX6ULL驱动开发_编写第一个hello驱动【熬夜肝】,Linux驱动开发【IMX6ULL】,驱动开发,linux驱动,第一个驱动程序,IMX6ULL,insmod
我们用vscode找一下

【IMX6ULL驱动开发学习】05.IMX6ULL驱动开发_编写第一个hello驱动【熬夜肝】,Linux驱动开发【IMX6ULL】,驱动开发,linux驱动,第一个驱动程序,IMX6ULL,insmod
发现 register_chrdev 函数需要三个参数
第一个参数是主设备号,0代表动态分配
第二个参数是设备的名字(自定义)
第三个参数是struct file_operations结构体类型的指针,代表申请设备的操作函数

同理,出口函数 unregister_chrdev
第一个参数是设备号
第二个参数是设备名称

照葫芦画瓢开始写

/*入口函数*/
static int major;
static int hello_init(void)
{
	/*返回设备号,定义设备名称为hello_drv*/
	major = register_chrdev(0,"hello_drv",&hello_drv);
	return 0;
}

/*退出函数*/
static int hello_exit(void)
{
	unregister_chrdev(major,"hello_drv");
	return 0;
}	

7. module_init 、module_exit和声明许可证

module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");

整个 hello_drv.c 代码

#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/delay.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/capability.h>
#include <linux/init.h>
#include <linux/mutex.h>
#include <asm/mach-types.h>
#include <asm/uaccess.h>
#include <asm/therm.h>

static int major;

static int hello_open (struct inode *node, struct file *filp)
{
	printk("hello_open\n");
	printk("%s %s %d\n",__FILE__, __FUNCTION__, __LINE__);
	return 0;
}

static ssize_t hello_read (struct file *filp, char *buf, size_t size, loff_t *offset)
{
	printk("hello_read\n");
	return size;  //返回读取字节数
}

static ssize_t hello_write (struct file *filp, const char *buf, size_t size, loff_t *offset)
{
	printk("hello_write\n");
	return size;  //返回写入字节数
}

static int hello_release (struct inode *node, struct file *filp)
{
	printk("hello_release\n");
	return 0;
}

/*1.定义 file_operations 结构体*/
static const struct file_operations hello_drv = {
    .owner 		= THIS_MODULE,
	.read		= hello_read,
	.write		= hello_write,
	.open		= hello_open,
	.release    = hello_release,
};


/*2.register_chrdev*/

/*3.入口函数*/
static int hello_init(void)
{
	//设备号
	major = register_chrdev(0,"hello_drv",&hello_drv);
	return 0;
}

/*4.退出函数*/
static int hello_exit(void)
{
	//卸载驱动
	unregister_chrdev(major,"hello_drv");
	return 0;
}	

module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");

8. 编写Makefile

不多说,直接上代码

KERN_DIR = /home/me/Linux-4.9.88

PWD ?= $(shell KERN_DIR)

all:	
	make -C $(KERN_DIR) M=$(PWD) modules
	#$(CROSS_COMPILE)gcc -o hello_test hello_test.c
clean:
	make -C $(KERN_DIR) M=$(PWD) modules clean
	rm -rf modules.order
	rm -f hello_drv

obj-m += hello_drv.o

KERN_DIR = /home/me/Linux-4.9.88 : 编译程序的依赖目录

9. make一下,生成.ko文件

me@ubuntu:~/Linux_ARM/IMX6ULL/hello_driver$ ls
hello_drv.c  hello_drv.ko  hello_drv.mod.c  hello_drv.mod.o  hello_drv.o  
hello_test  hello_test.c  Makefile  modules.order  Module.symvers

主要就是.ko文件,就是加载到内核里的驱动模块文件


五、IMX6ULL验证hello驱动

1. 将虚拟机编译生成的.ko文件拷贝到IMX6ULL开发板上
这步操作需要虚拟机和开发板在同一网段下,可以借鉴以下两篇博客
03.设置IMX6ULL开发板与虚拟机在同一网段
04.IMX6ULL开发板与虚拟机互传文件
我采用的是NFS挂载的方式

mount -t nfs -o nolock,vers=3 192.168.1.200:/home/me/Linux_ARM/IMX6ULL/hello_driver /mnt

【IMX6ULL驱动开发学习】05.IMX6ULL驱动开发_编写第一个hello驱动【熬夜肝】,Linux驱动开发【IMX6ULL】,驱动开发,linux驱动,第一个驱动程序,IMX6ULL,insmod
如果出现报错

failed: Device or resource busy

执行

unmount /mnt

2.加载内核
执行命令加载内核

insmod hello_drv.ko
[root@100ask:/mnt]# insmod hello_drv.ko 
[   80.794911] hello_drv: loading out-of-tree module taints kernel.
[root@100ask:/mnt]#

显示已载入系统的模块

lsmod

【IMX6ULL驱动开发学习】05.IMX6ULL驱动开发_编写第一个hello驱动【熬夜肝】,Linux驱动开发【IMX6ULL】,驱动开发,linux驱动,第一个驱动程序,IMX6ULL,insmod
查看 hello_drv.ko 驱动模块的设备号

cat /proc/devices
226 drm
240 hello_drv
241 adxl345
242 spidevx
243 irda
244 dht11

可以看到hello驱动的设备号是240

3.生成设备节点

mknod /dev/hello c 240 0

/dev/hello : 生成设备节点的名称
c :说明是字符设备
240 : 主设备号
0 : 子设备号(不指定子设备号,为0)

这时候查看hello设备也可以用下面的命令

[root@100ask:/mnt]# ls /dev/hello -l
crw-r--r-- 1 root root 240, 0 Jan  1 11:34 /dev/hello

4.编写应用程序验证驱动

PS:在第四节第8小节中,将 $(CROSS_COMPILE)gcc -o hello_test hello_test.c 这一行注释去掉

编写 hello_test.c 应用程序

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

int main(int argc, char *argv[])
{
    int len;
    char read_buf[10];

    if(argc < 2){
        printf("please input  at least 2 args\n");
        printf("%s <dev> [string]\n", argv[0]);
        return -1;
    }

    /*open*/
    int fd;
    fd = open(argv[1], O_RDWR);
    if(fd < 0){
        printf("open failed\n");
        return -2;
    }

    /*read*/
    if(argc == 2){
        read(fd, read_buf, 10);    //调用read函数,只为了触发系统调用hello驱动的read函数
        printf("read operation \n");
    }

    /*write*/
    if(argc == 3){
        len = write(fd, argv[2], strlen(argv[2]));   //调用write函数,只为了触发系统调用hello驱动的write函数
        printf("write length = %d \n", len);
    }

    close(fd);

    return 0;
}

该程序的使用方式:

./hello_test   /dev/hello  123abc     两个参数:模拟写操作
./hello_test   /dev/hello             一个参数:模拟读操作

/dev/hello 是我们要打开的设备名称,在应用程序中,使用 open 函数打开,使用 close 函数关闭

如果打开成功,则系统会调用 hello 驱动的 open 函数,我们就会看到相应的打印信息(打印出当前文件名,函数名,行数)

static int hello_open (struct inode *node, struct file *filp)
{
	printk("hello_open\n");
	printk("%s %s %d\n",__FILE__, __FUNCTION__, __LINE__);
	return 0;
}

注意驱动程序中使用的都是printk函数,该函数是内核调用的,想要在开发板的串口打印信息中看到输出,需要执行以下命令

echo "7 4 1 7" > /proc/sys/kernel/printk

测试驱动写操作,成功!!!

[root@100ask:/mnt]# ./hello_test /dev/hello 
[  499.512588] hello_open
[  499.516872] /home/me/Linux_ARM/IMX6ULL/hello_driver/hello_drv.c hello_open 28
[  499.525082] hello_read
read operation [  499.528427] hello_release

[root@100ask:/mnt]# 

测试驱动读操作,成功!!!

[root@100ask:/mnt]# ./hello_test /dev/hello abc123
[  500.725340] hello_open
[  500.727762] /home/me/Linux_ARM/IMX6ULL/hello_driver/hello_drv.c hello_open 28
[  500.736217] hello_write
write length = 6 [  500.739735] hello_release

[root@100ask:/mnt]# 

【IMX6ULL驱动开发学习】05.IMX6ULL驱动开发_编写第一个hello驱动【熬夜肝】,Linux驱动开发【IMX6ULL】,驱动开发,linux驱动,第一个驱动程序,IMX6ULL,insmod文章来源地址https://www.toymoban.com/news/detail-735606.html

到了这里,关于【IMX6ULL驱动开发学习】05.IMX6ULL驱动开发_编写第一个hello驱动【熬夜肝】的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【IMX6ULL驱动开发学习】22.IMX6ULL开发板读取ADC(以MQ-135为例)

    IMX6ULL一共有两个ADC,每个ADC都有八个通道,但他们共用一个ADC控制器 在imx6ull.dtsi文件中已经帮我们定义好了adc1的节点部分信息 注意 num-channels = 2; ,这个表示指定使用ADC1的两个通道,即通道1和通道2 如果你要使用多个ADC通道,修改这个值即可 配置ADC引脚的 pinctrl ,在自己的

    2024年02月12日
    浏览(46)
  • 【IMX6ULL驱动开发学习】15.IMX6ULL驱动开发问题记录(sleep被kill_fasync打断)

    发现问题的契机: 学习异步通知的时候,自己实现一个功能:按键控制蜂鸣器,同时LED灯在闪烁 结果:LED好像也同时被按键控制了 最后调试结果发现: 应用层的 sleep 被驱动层的 kill_fasync 打断,所以sleep没有执行完就重新进入下一次循环了 修改代码后解决该问题 解决逻辑就

    2024年02月13日
    浏览(42)
  • 【IMX6ULL驱动开发学习】08.IMX6ULL通过GPIO子系统函数点亮LED

    通过GPIO子系统函数点亮LED 1、GPIO子系统函数 1.1 确定 led 的GPIO标号,查看内核中的gpiochip 查看 gpiochip ,以正点原子的IMX6ULL阿尔法开发板为例 查看原理图,发现led接的引脚是 GPIO1_IO3,对应 /sys/kernel/debug/gpio 中的 gpiochip0 组,gpiochip0 组从0开始算起, 所以 GPIO1_IO3 对应的标号就

    2024年02月10日
    浏览(66)
  • 【IMX6ULL驱动开发学习】03.设置IMX6ULL开发板与虚拟机在同一网段(设置开发板静态IP)

    为什么要设置IMX6ULL与虚拟机通信? 因为要把在虚拟机下编译的文件传到IMX6ULL开发板上运行 设置好同一网段,可以互ping后,可以参考这篇博客,实现开发板与虚拟机的文件互传 IMX6ULL开发板与虚拟机互传文件 一、设置windows有线网卡 二、配置虚拟机双网卡(原本有一个NAT网卡

    2024年02月07日
    浏览(42)
  • 【IMX6ULL驱动开发学习】19.mmap内存映射

    mmap将一个文件或者其它对象映射进内存 ,使得应用层可以直接读取到驱动层的数据,无需通过copy_to_user函数 可以用于像LCD这样的外设, 需要读写大量数据的 一、应用层 mmap用法: 用open系统调用打开文件, 并返回描述符fd. 用mmap建立内存映射, 并返回映射首地址指针start. 对映

    2024年02月16日
    浏览(39)
  • 【IMX6ULL驱动开发学习】12.Linux驱动之设备树

    承接上一篇博客 【IMX6ULL驱动开发学习】11.驱动设计之面向对象_分层思想(学习设备树过渡部分) 代码获取: https://gitee.com/chenshao777/imx6-ull_-drivers 我后面将三个层合并了(实际上只有前两层),合并成一个dev_drv.c了,暂时没有加GPIO操作,只是个框架 合并前的代码在 11.butt

    2024年02月13日
    浏览(36)
  • 【IMX6ULL驱动开发学习】11.Linux之SPI驱动

    参考:驱动程序开发:SPI设备驱动_spi驱动_邓家文007的博客-CSDN博客 目录 一、SPI驱动简介 1.1 SPI架构概述 1.2 SPI适配器(控制器)数据结构 1.2 SPI设备数据结构 1.3 SIP设备驱动 1.4 接口函数  二、SPI驱动模板 SPI驱动框架和I2C驱动框架是十分相似的,不同的是因为SPI是通过片选引

    2024年02月11日
    浏览(40)
  • iMX6ULL驱动开发 | 让imx6ull开发板支持usb接口FC游戏手柄

    手边有一闲置的linux开发板iMX6ULL一直在吃灰,不用来搞点事情,总觉得对不住它。业余打发时间就玩起来吧,总比刷某音强。从某多多上买来一个usb接口的游戏手柄,让开发板支持以下它,后续就可以接着在上面玩童年经典游戏啦。  我使用的是正点原子的I.MX6U-ALPHA 开发板,

    2024年02月14日
    浏览(45)
  • 【IMX6ULL驱动开发学习】12.Linux SPI驱动实战:DAC驱动设计流程

    基础回顾: 【IMX6ULL驱动开发学习】10.Linux I2C驱动实战:AT24C02驱动设计流程_阿龙还在写代码的博客-CSDN博客 【IMX6ULL驱动开发学习】11.Linux之SPI驱动_阿龙还在写代码的博客-CSDN博客 查看芯片手册,有两种DAC数据格式,12位和16位,这里选用16位数据(2字节)编写驱动。  重点在

    2024年02月11日
    浏览(43)
  • 【IMX6ULL驱动开发学习】11.驱动设计之面向对象_分层思想(学习设备树过渡部分)

    一个 可移植性好 的驱动程序,应该有三个部分组成 1、驱动框架程序(xxx_drv.c) — 对接应用层的 open read write 函数,不做GPIO具体操作 2、硬件操作程序(xxx_chip_gpio.c)— 执行具体的GPIO操作,初始化、读写 3、硬件资源定义程序(xxx_board.c,这在之后就过渡成了设备树)— 为

    2024年02月11日
    浏览(34)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包