Linux驱动开发-04LED灯驱动实验(直接操作寄存器)

news2024/9/20 14:50:39

一、Linux 下LED 灯驱动原理

Linux 下的任何外设驱动,最终都是要配置相应的硬件寄存器。驱动访问底层的硬件除了使用内存映射将物理地址空间转化为虚拟地址空间,去进行读写修改,还可以通过各种子系统函数去进行操作

1.1 地址映射

MMU 全称叫做Memory Manage Unit,也就是内存管理单元;MMU 主要完成的功能如下:
①完成虚拟空间到物理空间的映射。
②内存保护,设置存储器的访问权限,设置虚拟存储空间的缓冲特性。
我们重点来看一下第①点,也就是虚拟空间到物理空间的映射,也叫做地址映射。首先了解两个地址概念:虚拟地址(VA,Virtual Address)、物理地址(PA,Physcical Address)。对于32 位的处理器来说,虚拟地址范围是2^32=4GB。

        Linux内核启动的时候会初始化 MMU,设置好内存映射,设置好以后 CPU访问的都是虚拟地址。比如 I.MX6ULL的 GPIO1_IO03引脚的复用寄存器IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03的地址为 0X020E0068。如果没有开启 MMU的话直接向 0X020E0068这个寄存器地址写入数据就可以配置 GPIO1_IO03的复用功能。现在开启了 MMU,并且设置了内存映射,因此就不能直接向 0X020E0068这个地址写入数据了。我们必须得到 0X020E0068这个物理地址在 Linux系统里面对应的虚拟地址,这里就涉及到了物理内存和虚拟内存之间的转换,需要用到两个函数: ioremap和 iounmap。

1.2 I/O内存访问函数

当外部寄存器或内存映射到 IO空间时,称为 I/O端口。当外部寄存器或内存映射到内存空间时,称为 I/O内存。但是对于 ARM来说没有 I/O空间这个概念,因此 ARM体系下只有 I/O内存 (可以直接理解为内存 )使用 ioremap函数将寄存器的物理地址映射到虚拟地址以后,我们就可以直接通过指针访问这些地址,但是 Linux内核不建议这么做,而是推荐使用一组操作函数来对映射后的内存进行读写操作。

//1、读操作函数
u8 readb(const volatile void __iomem *addr) 
u16 readw(const volatile void __iomem *addr) 
u32 readl(const volatile void __iomem *addr)
/*readb、 readw和 readl这三个函数分别对应 8bit、 16bit和 32bit读操作,参数 addr就是要
读取写内存地址,返回值就是读取到的数据。*/
//2、写操作函数
void writeb(u8 value, volatile void __iomem *addr)
void writew(u16 value, volatile void __iomem *addr)
void writel(u32 value, volatile void __iomem *addr)
/*writeb、 writew和 writel这三个函数分别对应 8bit、 16bit和 32bit写操作,参数 value是要
写入的数值, addr是要写入的地址。*/

1.3 电路原理图

可以知道GPIO1_3的端口连接着led,我们还查看了数据手册和开发板,只有GPIO1组的几个端口是没有复用的,我们在开发板上可以直接使用

二、实战之驱动文件的编写

2.1 搭建相关的开发环境

(1)创建2_led驱动文件夹

(2)将1_chrdevbase的文件复制并重命名

//mv命令进行文件移动
mv [选项]... 源文件或目录... 目标文件或目录
//mv命令修改文件名
mv oldname.txt newname.txt

(3)使用vscode打开2_led驱动文件夹,并保存工作区

 (4).vscode文件也进行复制

 cp ./1_chrdevbase/.vscode/ ./2_led/ -rf

 2.2 查看数据手册

/*时钟GPIO1_IO03相关寄存器地址定义*/
/* 
 * CCM相关寄存器地址 
 */
#define CCM_CCGR0 			*((volatile unsigned int *)0X020C4068)
#define CCM_CCGR1 			*((volatile unsigned int *)0X020C406C)

#define CCM_CCGR2 			*((volatile unsigned int *)0X020C4070)
#define CCM_CCGR3 			*((volatile unsigned int *)0X020C4074)
#define CCM_CCGR4 			*((volatile unsigned int *)0X020C4078)
#define CCM_CCGR5 			*((volatile unsigned int *)0X020C407C)
#define CCM_CCGR6 			*((volatile unsigned int *)0X020C4080)

/* 
 * IOMUX相关寄存器地址 
 */
#define SW_MUX_GPIO1_IO03 	*((volatile unsigned int *)0X020E0068)
#define SW_PAD_GPIO1_IO03 	*((volatile unsigned int *)0X020E02F4)

/* 
 * GPIO1相关寄存器地址 
 */
#define GPIO1_DR 			*((volatile unsigned int *)0X0209C000)
#define GPIO1_GDIR 			*((volatile unsigned int *)0X0209C004)
#define GPIO1_PSR 			*((volatile unsigned int *)0X0209C008)
#define GPIO1_ICR1 			*((volatile unsigned int *)0X0209C00C)
#define GPIO1_ICR2 			*((volatile unsigned int *)0X0209C010)
#define GPIO1_IMR 			*((volatile unsigned int *)0X0209C014)
#define GPIO1_ISR 			*((volatile unsigned int *)0X0209C018)
#define GPIO1_EDGE_SEL 		*((volatile unsigned int *)0X0209C01C)

 2.3 代码

led.c

#include <linux/init.h> //包含宏定义的头文件(printk的头文件)
#include <linux/module.h> //包含初始化加载模块的头文件
#include <linux/fs.h>//注册设备和注销设备的头文件
#include<linux/kernel.h>
#include <linux/ide.h>
#include <linux/types.h>
#include <asm/io.h>//地址映射需要的头文件
/*
GPIO的初始化:
1.初始化GPIO的时钟
2.初始化IO复用
3.配置GPIO1_IO03的IO属性(速度、输出模式等)
4.初始化GPIO
5.设置输出的电平
*/

/*寄存器物理地址*/
#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)
/* 映射后的寄存器虚拟地址指针,类型为__iomem指针 */
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;

#define LED_MAJOR 200
#define LED_NAME "led"

#define LEDOFF 0//关灯
#define LEDON  1//开灯

static void led_switch(u8 sta){
    u32 val=0;
    if(sta == LEDON) {
		val = readl(GPIO1_DR);
		val &= ~(1 << 3);	/*bit 3清零,打开led灯*/
		writel(val, GPIO1_DR);
	}else if(sta == LEDOFF) {
		val = readl(GPIO1_DR);
		val|= (1 << 3);	/*bit 3清零,关闭led灯*/
		writel(val, GPIO1_DR);
	}
}

static ssize_t led_write(struct file *filp, const char __user *buf,size_t count, loff_t *ppos){
    /*这里的write需要接受来自应用程序的控制指令*/
    int retvalue;
	unsigned char databuf[1];

	retvalue = copy_from_user(databuf, buf, count);
	if(retvalue < 0) {
		printk("kernel write failed!\r\n");
		return -EFAULT;
	}

	led_switch(databuf[0]);	/* 判断是开灯还是关灯 */
    return 0;
}
static int led_open(struct inode *inode, struct file *filp){
    return 0;
}
static int led_close(struct inode *inode, struct file *filp){
    return 0;
}
/*字符设备操作集合*/
static const struct file_operations  led_fops={
	.owner	= THIS_MODULE,
	.write	= led_write,
	.open	= led_open,
	.release= led_close,
};
/*入口*/
static int __init led_init(void){
    int ret=0;//变量的声明需要放在代码的最前面才不会有警告
    unsigned int  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);

    /*1.注册字符设备*/
    ret=register_chrdev(LED_MAJOR, LED_NAME,&led_fops);
    if (ret<0)
    {
        printk("register chrdev failed\r\n");
        return -EIO;/* I/O error */
    }
    
    printk("led_init\r\n");
    return 0;
}
/*出口*/
static void __exit led_exit(void){//_exit这个只是一个修饰符

    unsigned int  val = 0;
    val = readl(GPIO1_DR);
	val |= (1 << 3);	
	writel(val, GPIO1_DR);

    /*取消地址映射*/
    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);
    printk("led_exit\r\n");
}

/*驱动注册加载和卸载*/
module_init(led_init);
module_exit(led_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Chao");

 ledAPP.c

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"

/*
/dev/chardevbase:驱动最终表现就是/dev/xxx文件,对文件的读写、打开关闭
argc:应用程序参数
argv[]:具体的参数内容,是以字符串形式
./chrdevbaseAPP  <filename>  <1:2>
./chrdevbaseAPP /dev/led 0    关灯
./chrdevbaseAPP /dev/led 1    开灯
*/

int main(int argc,char*argv[]){
    int fd=0;
    int ret=0;
    char*filename;
    unsigned char databuf[1];

    if (argc!=3)
    {
        printf("Error Usage\r\n");
        return -1;
    }
    
    filename=argv[1];
    fd=open(filename,O_RDWR);
    if (fd<0)
    {
        printf("file %s open failed\r\n",filename);
        return -1;
    }
    databuf[0]=atoi(argv[2]);//将字符转化为数字
    ret = write(fd,databuf,sizeof(databuf));
    if (ret<0)
    {
        printf("LED Control Failed!\r\n");
        close(fd);
        return -1;
    }
    
    close(fd);
    return 0;
}

 2.4 实验结果

(1)装载驱动和手动注册设备节点

 (2)输入指令测试结果

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

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

相关文章

WebSocket实现群聊功能、房间隔离

引用WebSocket相关依赖 <dependency><groupId>javax.websocket</groupId><artifactId>javax.websocket-api</artifactId><version>1.1</version></dependency><dependency><groupId>org.springframework</grou…

Golang | Leetcode Golang题解之第235题二叉搜索树的最近公共祖先

题目&#xff1a; 题解&#xff1a; func lowestCommonAncestor(root, p, q *TreeNode) (ancestor *TreeNode) {ancestor rootfor {if p.Val < ancestor.Val && q.Val < ancestor.Val {ancestor ancestor.Left} else if p.Val > ancestor.Val && q…

【区块链 + 智慧政务】基于区块链的可信数据档案管理系统 | FISCO BCOS应用案例

目前&#xff0c;我国的档案管理整体上实行“电子化”和“纸质”同步并存的“双套制”管理体系&#xff0c;这套管理规范体系在一 定程度上满足了电子文件安全存储的要求。但是随着云计算、大数据、区块链等现代信息技术的快速发展&#xff0c;以及 全国“互联网 政务服务”工…

TG创建小程序交互APP登录以及机器人信息

1、搜索 BotFather &#xff0c;输入命令 /newbot 创建机器人。 2、修改机器人信息 /mybots 编辑名称 : 修改机器人名称 编辑关于: 修改关于 hayden yyds&#xff0c;修改以后打开机器人会出现在下图 编辑描述 : 机器人的描述 编辑描述图片 : 机器人的图片 编辑 Botpic…

让前端和后端要“动手”的7大行为,你中了几个。

前后端分离导致了二者在工作中交叉非常多&#xff0c;不像之前前端搞完静态页面&#xff0c;拽给后端就行了。 这种交叉必然导致巨大的沟通成本&#xff0c;贝格前端工场将项目踩过的坑&#xff0c;给大家分享一下。 前端工程师最讨厌的后端行为包括&#xff1a; 1. 不提供清…

高端的食材,往往只需要最简单的烹饪!ORB,仅此一招,Alpha达到年化36%

常常看到有人提问&#xff0c;如何挖掘因子和策略&#xff1f;ORB 策略的改进历史能给我们许多启发。一是一个策略值得研究数十年&#xff1b;二是温故而知新是永远的法宝。沉下心来&#xff0c;真正吃透 IT 系统、吃透数据和已有策略&#xff0c;比追风要好得多。 炒股要炒大…

备考美国数学竞赛AMC10:吃透1200道历年真题和知识点(持续)

距离2024年AMC10美国数学竞赛开赛预计还有3个多月的时间&#xff0c;实践证明&#xff0c;做真题&#xff0c;吃透真题和背后的知识点是备考AMC8、AMC10有效的方法之一。 通过做真题&#xff0c;可以帮助孩子找到真实竞赛的感觉&#xff0c;而且更加贴近比赛的内容&#xff0c…

[AHK] WinHttpRequest.5.1报错 0x80092004 找不到对象或属性

目录 背景描述 用浏览器访问&#xff0c;正常返回 ​编辑 AHK v2官方示例源代码 AHK v2运行结果报错(0x80092004) 找不到对象或属性 用thqby大佬的WinHttpRequest.ahk库测试报错 0x80092004 找不到对象或属性 附&#xff1a; 用Apifox访问&#xff0c;也正常返回 AHK v1 …

怎样优化 PostgreSQL 中对复杂查询的并行执行计划?

&#x1f345;关注博主&#x1f397;️ 带你畅游技术世界&#xff0c;不错过每一次成长机会&#xff01;&#x1f4da;领书&#xff1a;PostgreSQL 入门到精通.pdf 文章目录 怎样优化 PostgreSQL 中对复杂查询的并行执行计划一、了解并行执行计划的基础知识二、优化并行执行计划…

MySQL索引特性(上)

目录 索引的重要 案例 认识磁盘 MySQL与存储 先来研究一下磁盘 扇区 定位扇区 结论 磁盘随机访问与连续访问 MySQL与磁盘交互基本单位 建立共识 索引的理解 建立测试表 插入多条记录 局部性原理 所有的MySQL的操作(增删查改)全部都是在MySQL当中的内存中进行的&am…

基于AT89C51单片机GSM模块的家庭防火防盗报警系统设计(含文档、源码与proteus仿真,以及系统详细介绍)

本篇文章论述的是基于AT89C51单片机GSM模块的家庭防火防盗报警系统设计的详情介绍&#xff0c;如果对您有帮助的话&#xff0c;还请关注一下哦&#xff0c;如果有资源方面的需要可以联系我。 目录 摘 要 原理图 仿真图 元器件清单 代码 系统论文 参考文献 资源下载…

内衣专用洗衣机怎么样?五样超卓臻品专业推荐!

在日常生活中&#xff0c;内衣洗衣机已成为现代家庭必备的重要家电之一。选择一款耐用、质量优秀的内衣洗衣机&#xff0c;不仅可以减少洗衣负担&#xff0c;还能提供高效的洗涤效果。然而&#xff0c;市场上众多内衣洗衣机品牌琳琅满目&#xff0c;让我们往往难以选择。那么&a…

【Python】Selenium怎么切换浏览器的页面

我们在爬网使用Selenium进行测试的时候&#xff0c;有时候想要点击浏览器里面的网址&#xff0c;跳到另一个页面上&#xff0c;获取第二个页面的内容。 可是有时候从官网进去&#xff0c;点击跳转到下一个页面以后&#xff0c;却没法定位到下一个页面的元素&#xff0c;这时候就…

【JVM基础01】——介绍-初识JVM运行流程

目录 1- 引言&#xff1a;初识JVM1-1 JVM是什么&#xff1f;(What)1-1-1 概念1-1-2 优点 1-2 为什么学习JVM?(Why) 2- 核心&#xff1a;JVM工作的原理&#xff08;How&#xff09;⭐2-1 JVM 的组成部分及工作流程2-2 学习侧重点 3- 小结(知识点大纲)&#xff1a;3-1 JVM 组成3…

Ubuntu 22.04.4 LTS (linux) 安装certbot 免费ssl证书申请 letsencrypt

1 安装certbot sudo apt update sudo apt-get install certbot 2 申请letsencrypt证书 sudo certbot certonly --webroot -w 网站目录 -d daloradius.域名.com 3 修改nginx 配置ssl 证书 # 配置服务器证书 ssl_certificate /etc/letsencrypt/live/daloradius.域名.com/f…

FPGA:基于复旦微FMQL10S400 /FMQL20S400 国产化核心板

复旦微电子是国内集成电路设计行业的领军企业之一&#xff0c;早在2000年就在香港创业板上市&#xff0c;成为行业内首家上市公司。公司的RFID芯片、智能卡芯片、EEPROM、智能电表MCU等多种产品在市场上的占有率位居行业前列。 今天介绍的是搭载复旦微 FMQL10S400/FMQL20S400的…

16001.WSL2 ubuntu20.04 编译安装 vsomeip

文章目录 1 vsomeip 编译安装1.1 vsomeip的安装1.2 编译提示错误1.3 编译hello_world示例1.4 运行服务器端 1 vsomeip 编译安装 1.1 vsomeip的安装 参考博文 https://blog.csdn.net/peterwanye/article/details/128386539 1.2 编译提示错误 ubuntu1-BJ-EE1000042:~/opt/vso…

【持续集成_05课_Linux部署SonarQube及结合开发项目部署】

一、Linux下安装SonarQube 1、安装sonarQube 前置条件&#xff1a;sonarQube不能使用root账号进行启动&#xff0c;所以需要创建普通用户及 其用户组 1&#xff09;创建组 2&#xff09;添加用户、组名、密码 3&#xff09;CMD上传qube文件-不能传到home路径下哦 4&#xff09…

【NLP大模型】词嵌入的空间表示与应用

文章目录 一、语义特征空间二、引入新维度&#xff1a;皇室三、语义特征向量的用途四、向量运算类比五、词嵌入的维度和应用词嵌入的应用 六、测量欧几里得距离向量计算向量和欧几里得距离 七、使用点积测量相似度八、创建词嵌入 一、语义特征空间 考虑“男人”、“女人”、“…

安防监控平台LntonAIServer视频监控管理平台裸土检测算法核心优势和应用场景

LntonAIServer裸土检测算法是一种基于人工智能技术的创新解决方案&#xff0c;旨在实现对裸土地表的自动识别。以下是对该算法的详细解析&#xff1a; 一、技术原理 LntonAIServer裸土检测算法利用深度学习和计算机视觉技术&#xff0c;通过捕捉视频或图像中的关键信息&#…