Linux驱动入门 —— LED点灯驱动程序

news2024/12/28 5:22:57

目录

IMX6ULL 的 GPIO 操作方法

GPIO 操作相关名词

IMX6ULL 的 GPIO 模块结构

GPIO 模块内部

读 GPIO​编辑

写 GPIO​编辑

LED 点灯驱动程序

字符设备驱动程序框架

编写驱动程序的步骤:

先编写驱动程序代码:

再编写测试程序代码:

Makefile

上机实验


IMX6ULL 的 GPIO 操作方法

GPIO 操作相关名词

IMX6ULL 的 GPIO 模块结构

参考芯片手册

有 5 组 GPIO(GPIO1~GPIO5),每组引脚最多有 32 个,但是可能实际上并有那么多。

  • GPIO1 有 32 个引脚:GPIO1_IO0~GPIO1_IO31;
  • GPIO2 有 22 个引脚:GPIO2_IO0~GPIO2_IO21;
  • GPIO3 有 29 个引脚:GPIO3_IO0~GPIO3_IO28;
  • GPIO4 有 29 个引脚:GPIO4_IO0~GPIO4_IO28;
  • GPIO5 有 12 个引脚:GPIO5_IO0~GPIO5_IO11;
  • GPIO 的控制涉及 4 大模块:CCM、IOMUXC、GPIO 模块本身,框图如图 4.2

1.2 CCM 用于设置是否向 GPIO 模块提供时钟

GPIOx 要用 CCM_CCGRy 寄存器中的 2 位来决定该组 GPIO 是否使能。

哪组 GPIO 用哪个 CCM_CCGR 寄存器来设置,请看上图红框部分。

CCM_CCGR 寄存器中某 2 位的取值含义如下:

  • 00:该 GPIO 模块全程被关闭
  • 01:该 GPIO 模块在 CPU run mode 情况下是使能的;在 WAIT 或 STOP 模式下,关闭
  • 10:保留
  • 11:该 GPIO 模块全程使能

GPIO2 时钟控制:

GPIO1、GPIO5 时钟控制:

GPIO3 时钟控制:

GPIO4 时钟控制:

IOMUXC:引脚的模式(Mode、功能)

对于某个/某组引脚,IOMUXC 中有 2 个寄存器用来设置它:

选择功能:

a) IOMUXC_SW_MUX_CTL_PAD_ <PADNAME>:Mux pad xxx,选择 某个 pad 的功能

b) IOMUXC_SW_MUX_CTL_GRP_<GROUP NAME>:Mux grp xxx,选 择某组引脚的功能 某个引脚,或是某组预设的引脚,都有 8 个可选的模式(alternate (ALT) MUX_MODE)。

比如:

设置上下拉电阻等参数:

a) IOMUXC_SW_PAD_CTL_PAD_<PAD_NANE>:pad pad xxx,设置某个 pad 的参数

b) IOMUXC_SW_PAD_CTL_GRP_<GROUP NAME>:pad grp xxx,设置某组引脚的参数

比如:

GPIO 模块内部

框图如下:

我们暂时只需要关心3个寄存器

GPIOx_GDIR:设置引脚方向,每位对应一个引脚,1-output,0-input

GPIOx_DR:设置输出引脚的电平,每位对应一个引脚,1-高电平,0-低电平

GPIOx_PSR:读取引脚的电平,每位对应一个引脚,1-高电平,0-低电平

读 GPIO

翻译一下:

  • 设置 CCM_CCGRx 寄存器中某位使能对应的 GPIO 模块 // 默认是使能的,上图省略了
  • 设置 IOMUX 来选择引脚用于 GPIO
  • 设置 GPIOx_GDIR 中某位为 0,把该引脚设置为输入功能
  • 读 GPIOx_DR 或 GPIOx_PSR 得到某位的值(读 GPIOx_DR 返回的是 GPIOx_PSR 的值)

写 GPIO

翻译一下:

  • 设置 CCM_CCGRx 寄存器中某位使能对应的 GPIO 模块 // 默认是使能 的,上图省略了
  • 设置 IOMUX 来选择引脚用于 GPIO
  • 设置 GPIOx_GDIR 中某位为 1,把该引脚设置为输出功能
  • 写 GPIOx_DR 某位的值

需要注意的是,你可以设置该引脚的 loopback 功能,这样就可以从 GPIOx_PSR 中读到引脚的有实电平;你从 GPIOx_DR 中读回的只是上次设置的 值,它并不能反应引脚的真实电平,比如可能因为硬件故障导致该引脚跟地短路了,你通过设置 GPIOx_DR 让它输出高电平并不会起效果

LED 点灯驱动程序

字符设备驱动程序框架

字符设备驱动程序的框架如下:

编写驱动程序的步骤:

  1. 确定主设备号,也可以让内核分配
  2. 定义自己的 file_operations 结构体
  3. 实现对应的 drv_open/drv read/drv write 等函数,填入 file operations 结构体
  4. 把 file_operations 结构体告诉内核: register_chrdev
  5. 谁来注册驱动程序啊? 得有一个入口函数:安装驱动程序时,就会去调用这个入口函数
  6. 有入口函数就应该有出口函数: 卸载驱动程序时,出口函数调用unregister_chrdev
  7. 其他完善:提供设备信息,自动创建设备节点: class_create,device_create

驱动怎么操作硬件?

  • 通过 ioremap 映射寄存器的物理地址得到虚拟地址,读写虚拟地址。

驱动怎么和 APP 传输数据?

  • 通过 copy_to_user、copy_from_user 这 2 个函数。

先编写驱动程序代码:

  • 实现 led_open 函数,在里面初始化 LED 引脚。
  • 实现 led_write 函数,在里面根据 APP 传来的值控制 LED。
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/delay.h>
#include <linux/poll.h>
#include <linux/mutex.h>
#include <linux/wait.h>
#include <linux/uaccess.h>
#include <linux/device.h>
#include <asm/io.h>

static int major;
static struct class *led_class;

/* registers */
// IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 地址:0x02290000 + 0x14
static volatile unsigned int *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3;

// GPIO5_GDIR 地址:0x020AC004
static volatile unsigned int *GPIO5_GDIR;

//GPIO5_DR 地址:0x020AC000
static volatile unsigned int *GPIO5_DR;

static ssize_t led_write(struct file *filp, const char __user *buf,
			 size_t count, loff_t *ppos)
{
	char val;
	int ret;
	
	/* copy_from_user : get data from app */
	ret = copy_from_user(&val, buf, 1);

	/* to set gpio register: out 1/0 */
	if (val)
	{
		/* set gpio to let led on */
		*GPIO5_DR &= ~(1<<3);
	}
	else
	{

		/* set gpio to let led off */
		*GPIO5_DR |= (1<<3);
	}
	return 1;
}

static int led_open(struct inode *inode, struct file *filp)
{
	/* enable gpio5
	 * configure gpio5_io3 as gpio
	 * configure gpio5_io3 as output 
	 */
	*IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 &= ~0xf;
	*IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 |= 0x5;

	*GPIO5_GDIR |= (1<<3);
	
	return 0;
}

static struct file_operations led_fops = {
	.owner		= THIS_MODULE,
	.write		= led_write,
	.open		= led_open,
};

/* 入口函数 */
static int __init led_init(void)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	
	major = register_chrdev(0, "zgl_led", &led_fops);

	/* ioremap */
	// IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 地址:0x02290000 + 0x14
	IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = ioremap(0x02290000 + 0x14, 4);
	
	// GPIO5_GDIR 地址:0x020AC004
	GPIO5_GDIR = ioremap(0x020AC004, 4);
	
	//GPIO5_DR 地址:0x020AC000
	GPIO5_DR  = ioremap(0x020AC000, 4);

	led_class = class_create(THIS_MODULE, "myled");
	device_create(led_class, NULL, MKDEV(major, 0), NULL, "myled"); /* /dev/myled */
	
	return 0;
}

static void __exit led_exit(void)
{
	iounmap(IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3);
	iounmap(GPIO5_GDIR);
	iounmap(GPIO5_DR);
	
	device_destroy(led_class, MKDEV(major, 0));
	class_destroy(led_class);
	
	unregister_chrdev(major, "zgl_led");
}

module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");

再编写测试程序代码:

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


// ledtest /dev/myled on
// ledtest /dev/myled off

int main(int argc, char **argv)
{
	int fd;
	char status = 0;
	
	if (argc != 3)
	{
		printf("Usage: %s <dev> <on|off>\n", argv[0]);
		printf("  eg: %s /dev/myled on\n", argv[0]);
		printf("  eg: %s /dev/myled off\n", argv[0]);
		return -1;
	}
	// open
	fd = open(argv[1], O_RDWR);
	if (fd < 0)
	{
		printf("can not open %s\n", argv[0]);
		return -1;
	}

	// write
	if (strcmp(argv[2], "on") == 0)
	{
		status = 1;
	}

	write(fd, &status, 1);
	return 0;	
}

Makefile

# 1. 使用不同的开发板内核时, 一定要修改KERN_DIR
# 2. KERN_DIR中的内核要事先配置、编译, 为了能编译内核, 要先设置下列环境变量:
# 2.1 ARCH,          比如: export ARCH=arm64
# 2.2 CROSS_COMPILE, 比如: export CROSS_COMPILE=aarch64-linux-gnu-
# 2.3 PATH,          比如: export PATH=$PATH:/home/book/100ask_roc-rk3399-pc/ToolChain-6.3.1/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin 
# 注意: 不同的开发板不同的编译器上述3个环境变量不一定相同,
#       请参考各开发板的高级用户使用手册

KERN_DIR = /home/book/100ask_imx6ull-sdk/Linux-4.9.88

all:
	make -C $(KERN_DIR) M=`pwd` modules 
	$(CROSS_COMPILE)gcc -o ledtest ledtest.c 

clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	rm -rf modules.order
	rm -f ledtest

obj-m	+= led_drv.o

上机实验

执行 make 命令编译驱动程序和测试程序

启动单板后,可以通过 NFS 挂载 Ubuntu 的某个目录,访问该目录中的程序。

具体挂载步骤可以看我之前写过的文章 开发板挂载 Ubuntu 的 NFS 目录-CSDN博客

打开内核打印:echo "7 4 1 7" > /proc/sys/kernel/printk

insmod led_drv.ko // 装载驱动

ls /dev/myled // 驱动程序会生成设备节点

cat /proc/devices,查看当前已经被使用掉的设备号

驱动名字与我们在驱动层使用register_chrdev()函数的第二个参数有关

./ledtest /dev/myled on // 成功点灯

如果驱动使用完了,不要用了

可用 rmmod led_drv.ko 指令卸载驱动

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1306107.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【华为数据之道学习笔记】4-2信息架构原则:建立企业层面的共同行为准则

信息架构承载了企业如何管理数据资产的方法&#xff0c;需要从整个企业 层面制订统一的原则&#xff0c;这些原则不仅是对数据专业人员的要求&#xff0c;也是对业务的要求&#xff0c;因为业务才是真正的数据Owner。所以&#xff0c;公司所有业务部门都应该共同遵从信息架构原…

【人工智能】人工智能中的Agent:法律虚拟助手简单示例

人工智能中的Agent&#xff1a;法律虚拟助手简单示例 随着人工智能技术的发展&#xff0c;Agent&#xff08;代理&#xff09;的概念在这个领域中变得愈发重要。在人工智能的应用中&#xff0c;Agent可以是一个系统、软件或机器人&#xff0c;能够执行特定的任务&#xff0c;理…

【JNA与C++基本使用示例】

JNA中java与C使用注意事项和代码示例 JNA关系映射表使用案列注意代码示例C代码java代码 JNA关系映射表 使用案列 注意 JNA只支持C方式的dll使用C的char* 作为返回值时&#xff0c;需要返回的变量为malloc分配的地址C的strlen函数只获得除/0以外的字符串长度 代码示例 C代码…

jumpserver web终端无法展示资产信息

前言 最近搭建了jumpServer 然后在web终端无法展示资产信息 错误展示 我的资产列表里面是有资产的 解决办法&#xff1a;

element Transfer 穿梭框 内容太长显示不全,鼠标移动上去显示全部

element Transfer中文字太长会造成显示不全&#xff0c;然后加了个提示 我这边是加了个插槽的形式&#xff0c;根据长度判断的&#xff0c;这个有个弊端就是如果是手机号的话&#xff0c;可能会没有省略号&#xff0c;然后也会有个黑色的提示框 <el-transferfilterable :d…

基于SSM实现的精品课程网站

一、系统架构 前端&#xff1a;jsp | js | css | jquery | bootstrap 后端&#xff1a;spring | springmvc | mybatis 环境&#xff1a;jdk1.7 | mysql | maven | tomcat 二、代码及数据库 三、功能介绍 01. 登录页 02. web端-首页 03. web端-视频教程 04. web端-资料…

【React】在移动端使用iconfont图标库

1、首先将需要的图标加入购物车 2、点击右上角的购物车&#xff0c;将购物里的图标添加到项目中 3、点击添加项目就会有选择的项目&#xff0c;选择后保存 如果有项目可以直接选择需要添加的项目&#xff0c;没有的需要新建项目存放到项目中 4、确定之后进入我的项目中&…

【初阶C++】前言

C前言 1. 什么是C2. C发展史3. C的重要性4. 如何学习C 1. 什么是C C语言是结构化和模块化的语言&#xff0c;适合处理较小规模的程序。对于复杂的问题&#xff0c;规模较大的程序&#xff0c;需要高度的抽象和建模时&#xff0c;C语言则不合适。为了解决软件危机&#xff0c; …

网神 SecGate3600 authManageSet.cgi信息泄露漏洞复现

漏洞概述 网神SecGate 3600 authManageSet.cgi 接口存在敏感信息泄露漏洞&#xff0c;未授权得攻击者可以通过此漏洞获取控制台管理员用户名密码等凭据&#xff0c;可登录控制整个后台&#xff0c;使系统处于极不安全的状态 复现环境 FOFA&#xff1a;body"sec_gate_im…

开源 CAD 计算机辅助设计软件,基于 Node.js 开发,使用浏览器进行访问-供大家学习研究参考

下载&#xff1a;开源CAD计算机辅助设计软件&#xff0c;基于Node.js开发&#xff0c;使用浏览器进行访问-供大家学习研究参考资源-CSDN文库 https://download.csdn.net/download/weixin_43097956/88623022

昇腾910安装驱动出错,降低Centos7.6的内核版本

零、问题描述&#xff1a; 在安装Atlas800-9000服务器的驱动的时候&#xff0c;可能会出现错误&#xff1a;Dkms install failed, details in : /var/log/ascend_seclog/ascend_install.log 如下所示&#xff1a; [rootlocalhost ~]# ./Ascend-hdk-910-npu-driver_23.0.rc3_l…

Python和Beautiful Soup爬虫助力提取文本内容

大家好&#xff0c;网络爬虫是一项非常抢手的技能&#xff0c;收集、分析和清洗数据是数据科学项目中最重要的部分。今天介绍如何从链接中爬取高质量文本内容&#xff0c;我们使用迭代&#xff0c;从大约700个链接中进行网络爬取。如果想直接跳转到代码部分&#xff0c;可以在下…

如何通过IDM进行统一权限

IDM身份管理平台在当今企业信息化建设中扮演着至关重要的角色&#xff0c;它不仅仅关乎用户身份的管理&#xff0c;更牵涉到对企业内部各种资源的合理分配和访问控制。其中&#xff0c;统一权限是 IDM 的一个核心概念&#xff0c;它通过集中管理用户的身份和访问权限&#xff0…

【EventBus】EventBus源码浅析

二、EventBus源码解析 目录 1、EventBus的构造方法2、订阅者注册 2.1 订阅者方法的查找过程2.2 订阅者的注册过程1. subscriptionsByEventType 映射&#xff1a;2. typesBySubscriber 映射&#xff1a;2.3 总结订阅者的注册过程 3、事件的发送 3.1 使用Post提交事件3.2 使用p…

Baumer工业相机堡盟工业相机如何通过BGAPISDK获取相机的各种信息如SN/ID等等(C#)

Baumer工业相机堡盟工业相机如何通过BGAPISDK获取相机的各种信息如SN/ID等等&#xff08;C#&#xff09; Baumer工业相机Baumer工业相机通过SDK获取相关生产信息的技术背景通过SDK获取相机信息的代码分析获取Baumer工业相机相关信息Baumer工业相机相关参数信息获取的测试 Baume…

GoLang EASY 微服务游戏框架 01

1 Overview EASY 是一个go语言编写的框架&#xff0c;兼容性支持go版本1.19&#xff0c;go mod 方式构建管理。它是一个轻型&#xff0c;灵活&#xff0c;自定义适配强的微服务框架。 它支持多种网络协议TCP&#xff0c;websocket&#xff0c;UDP&#xff08;待完成&#xf…

MySQL笔记-第12章_MySQL数据类型精讲

视频链接&#xff1a;【MySQL数据库入门到大牛&#xff0c;mysql安装到优化&#xff0c;百科全书级&#xff0c;全网天花板】 文章目录 第12章_MySQL数据类型精讲1. MySQL中的数据类型2. 整数类型2.1 类型介绍2.2 可选属性2.2.1 M2.2.2 UNSIGNED2.2.3 ZEROFILL 2.3 适用场景2.4…

蓝桥杯第一场强者挑战赛(C)SOSdp

之前在cf上面接触过SOSdp&#xff08;子集dp&#xff09;&#xff0c;这里就碰到了。 思路&#xff1a; 异或运算即非进位加法运算&#xff0c;因此如果需要进位的话&#xff0c;那么就无法满足题意&#xff0c;因此条件弱化为不需要进位&#xff0c;也就是不存在同一位上面都是…

JVM面试

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 1.JVM 的整体结构2.类加载做了哪些事情?类加载器有哪些&#xff1f;双亲委派和沙箱安全 3.Java虚拟机栈是什么4.方法区的理解HotSpot 中方法区的演进方法区的内部结…

bind、call、apply 区别?

作用 call、apply、bind作用是改变函数执行时的上下文&#xff0c;简而言之就是改变函数运行时的this指向 那么什么情况下需要改变this的指向呢&#xff1f; 下面举个例子 var name "lucy"; var obj {name: "martin",say: function () {console.log(…