Linux驱动开发(六)---树莓派配合硬件进行字符驱动开发

2023-05-16

前文回顾

《Linux驱动开发(一)—环境搭建与hello world》
《Linux驱动开发(二)—驱动与设备的分离设计》
《Linux驱动开发(三)—设备树》
《Linux驱动开发(四)—树莓派内核编译》
《Linux驱动开发(五)—树莓派设备树配合驱动开发》
继续宣传一下韦老师的视频

70天30节Linux驱动开发快速入门系列课程【实战教学、技术讨论、直播答疑】

在这里插入图片描述

基础硬件知识

这里目的就是想通过驱动来配置GPIO的操作,算是能和硬件操作挂钩的最简单做法了,首先要把整个思路理清。
在这里插入图片描述

先介绍一下树莓派的GPIO。
安装wiringPI,用来支持gpio命令,这个命令可以用来查看树莓派的所有引脚数据
在这里插入图片描述
其中physical对应的就是开发板上的排插引脚

在这里插入图片描述

如果你用wiringPI,那就需要关注
在这里插入图片描述
我们这里是自己写驱动,所以关注的是芯片引脚
在这里插入图片描述
树莓派的GPIO配置需要看三类寄存器,共6+2+2=10个地址可能会被用到。

  • GPFSEL0-5
    配置输入输出方向,这个配置挺有意思,pin脚的配置占用三个bit,共有54个pin脚,所以用了6个地址寄存器。
  • GPSET0-1
    配置高低电平
  • GPCLR0-1
    输出清除寄存器

详细的配置可以参考Linux底层驱动之树莓派IO口操作
解释的特别详细。推荐推荐。
在这里插入图片描述

设备树编写

根据前面的介绍,我们如果要想修改某个节点,就需要指定相关的三个地址参数,那么我们在DTS中,就需要定义出这三个参数,然后我们的驱动就可以自动适配到引脚了,灵活性大大提高。

部分代码参考《树莓派开发—初识驱动开发》

我们也选择引脚pin17,需要计算出这个引脚的三个参数
GPFSEL,每三个bit负责一个引脚,所以每个寄存器最多提供10个引脚,那么pin17使用的是GPFSEL1
地址为0x7E20 0004

GPSET,每一个bit负责一个pin,所以使用的是GPSET0,地址为0x7E20 001C

GPCLR,也是每个bit负责一个pin,所以使用GPCLR0,地址是0x7E20 0028

在这里插入图片描述
最后,我们要进行一个虚拟地址与硬件地址的映射,这就是之前说的在内核层面,程序只能访问虚拟地址,所以要将上图中的硬件地址,转化为虚拟地址。
通过cat /proc/iomem 查看。

root@raspberrypi:/home/sunjin# cat /proc/iomem 
00000000-37ffffff : System RAM
  00008000-00efffff : Kernel code
  01000000-011dfbeb : Kernel data
3f006000-3f006fff : dwc_otg
3f007000-3f007eff : 3f007000.dma dma@7e007000
3f00a000-3f00a023 : 3f100000.watchdog watchdog@7e100000
3f00b840-3f00b87b : 3f00b840.mailbox mailbox@7e00b840
3f00b880-3f00b8bf : 3f00b880.mailbox mailbox@7e00b880
3f100000-3f100113 : 3f100000.watchdog watchdog@7e100000
3f101000-3f102fff : 3f101000.cprman cprman@7e101000
3f104000-3f10400f : 3f104000.rng rng@7e104000
3f200000-3f2000b3 : 3f200000.gpio gpio@7e200000
3f201000-3f2011ff : serial@7e201000
  3f201000-3f2011ff : 3f201000.serial serial@7e201000
3f202000-3f2020ff : 3f202000.mmc mmc@7e202000
3f212000-3f212007 : 3f212000.thermal thermal@7e212000
3f215000-3f215007 : 3f215000.aux aux@7e215000
3f300000-3f3000ff : 3f300000.mmcnr mmcnr@7e300000
3f980000-3f98ffff : dwc_otg

可以查看到
3f200000-3f2000b3 : 3f200000.gpio gpio@7e200000
我们可以认为
3f200000表示的就是硬件地址7e200000
那么前面的三个硬件寄存器地址,就需要转化为
3f20 0004
3f20 001c
3f20 0028
所以最终的DTS可以写成
在这里插入图片描述

编译DTB。烧写,然后看一下是否有device-tree

pgg@raspberrypi:~ $ ls /proc/device-tree/mygpio/
compatible  gpclr  gpfsel  gpset  name  pin  status

完全OK,不过事实证明,这块确实存在问题,看完你就明白了
在这里插入图片描述

驱动编写

还是以前面的驱动为框架。同时参考前面的博客,首先修改一下probe函数,获取三个关键参数

static int led_probe(struct platform_device *pdev)
{	
	int minor;
	int i = 0;
	const char *tmp_str;

	struct resource *res;
		
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);

	if (!pdev->dev.of_node)  /* 普通的platform_device */
	{
		printk("unsupport normal device\n");
		return -1;
	}
	else
	{
		unsigned int pinnum=0;
		
		of_property_read_u32(pdev->dev.of_node,  "gpfsel", GPFSEL);
		of_property_read_u32(pdev->dev.of_node,  "gpset", GPSET);
		of_property_read_u32(pdev->dev.of_node,  "gpclr", GPCLR);
		of_property_read_u32(pdev->dev.of_node,  "pin", &pinnum);


		//of_property_read_string(pdev->dev.of_node, "pin", &tmp_str);
		printk("pin[%d] =  0x%x 0x%x 0x%x\n",pinnum, GPFSEL,GPSET,GPCLR);
		minor = g_ledcnt;
		leds_desc[minor].pin =pinnum;
	}

	/* 记录引脚 */
	leds_desc[minor].minor = minor;

	/* 7.2 辅助信息 */
	/* 创建设备节点 */
	device_create(led_class, NULL, MKDEV(major, minor), NULL, "mygpio%d", minor); /* /dev/100ask_led0,1,... */
	
	platform_set_drvdata(pdev, &leds_desc[minor]);
	
	g_ledcnt++;
	
    return 0;
}

先编写一下,试试能不能获取到参数,编译模块,上传开发板!
在这里插入图片描述

pgg@raspberrypi:~/work/dirver $ sudo insmod mygpio.ko 
pgg@raspberrypi:~/work/dirver $ dmesg 
[   69.034322] drivers/char/mygpio.c led_init line 169
[   69.034815] drivers/char/mygpio.c led_probe 89
[   69.034844] pin[17] =  0x3f200004 0x3f20001c 0x3f200028
pgg@raspberrypi:~/work/dirver $ ls /dev/mygpio0 
/dev/mygpio0

没问题,pin脚和三个关键参数就获取到了。设备也创建好了。那么就开始修改open和write函数吧
open函数

static int led_drv_open (struct inode *node, struct file *file)
{
	int minor = iminor(node);
	
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);

	/* 根据次设备号初始化LED */
	printk("init led pin 0x%x as output\n", leds_desc[minor].pin);

	int pin_num=(leds_desc[minor].pin%10)*3;
	
    *GPFSEL &= ~(0x6 << pin_num);
	*GPFSEL |= (0x1 << pin_num);
	
	return 0;
}

注意这里计算偏移,因为已经知道了是哪个寄存器,每个寄存器只负责10个pin,所以要取余。
write函数

static ssize_t led_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
	int err;
	char status;
	struct inode *inode = file_inode(file);
	int minor = iminor(inode);
	int pin_num=leds_desc[minor].pin%32;
	
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	err = copy_from_user(&status, buf, 1);

	/* 根据次设备号和status控制LED */
	if(status == 1)
	{
		*GPSET |= 0x1<< pin_num;  //设置为1
		printk("set led pin %d as 1\n", leds_desc[minor].pin);

	}else if(status ==0)
	{
		*GPCLR |= 0x1<< pin_num; //清除
		printk("set led pin %d as 0\n", leds_desc[minor].pin);
	}
	else
	{
		printk("undo\n");
	}

	return 1;
}

注意这里取余要取32的余数。

加上用户程序,统一测试一下。
在这里插入图片描述
就爆了个大错
在这里插入图片描述
这个虚拟地址看来不对。没关系,继续修改。

重新修理

DTS不变
在这里插入图片描述

然后在获取参数后,转化为虚拟地址。在probe函数中,进行如下修改

of_property_read_u32(pdev->dev.of_node,  "gpfsel", &a);
of_property_read_u32(pdev->dev.of_node,  "gpset", &b);
of_property_read_u32(pdev->dev.of_node,  "gpclr", &c);
of_property_read_u32(pdev->dev.of_node,  "pin", &pinnum);


//of_property_read_string(pdev->dev.of_node, "pin", &tmp_str);
printk("pin[%d] =  0x%x 0x%x 0x%x\n",pinnum, a,b,c);

GPFSEL= (volatile unsigned int *)ioremap(a,4);
GPSET = (volatile unsigned int *)ioremap(b,4);
GPCLR = (volatile unsigned int *)ioremap(c,4);

重新更新dtb,更新模块,再次测试

pgg@raspberrypi:~/work/dirver $ sudo insmod mygpio.ko 
pgg@raspberrypi:~/work/dirver $ sudo ./userapp /dev/mygpio0 on
pgg@raspberrypi:~/work/dirver $ sudo ./userapp /dev/mygpio0 off
pgg@raspberrypi:~/work/dirver $ dmesg

[   71.570789] drivers/char/mygpio.c led_init line 192
[   71.571042] drivers/char/mygpio.c led_probe 106
[   71.571056] pin[17] =  0x3f200004 0x3f20001c 0x3f200028
[   82.604078] drivers/char/mygpio.c led_drv_open line 50
[   82.604116] init led pin 0x11 as output
[   82.604144] drivers/char/mygpio.c led_drv_write line 71
[   82.604160] set led pin 17 as 1
[   86.368982] drivers/char/mygpio.c led_drv_open line 50
[   86.369019] init led pin 0x11 as output
[   86.369047] drivers/char/mygpio.c led_drv_write line 71
[   86.369063] set led pin 17 as 0

完美没毛病。不过到底引脚是不是1,手里没有万用表,只能用舌头试一下了。
在这里插入图片描述

回头望月

那么回头看一下,之前根据iomem查看的地址,为什么不对,那么我们打印出真实有效的地址看一下吧

GPFSEL= (volatile unsigned int *)ioremap(a,4);
GPSET = (volatile unsigned int *)ioremap(b,4);
GPCLR = (volatile unsigned int *)ioremap(c,4);

printk("real addr 0x%x 0x%x 0x%x\n",(unsigned int)GPFSEL,(unsigned int)GPSET,(unsigned int)GPCLR);

打印发现
[ 71.571072] real addr 0xb88e2004 0xb88e401c 0xb88e6028
所以完整的内存访问方式应该是

在这里插入图片描述
从文档,到程序,需要三步转化。
这个过程很重要

结束语

今天的社评暂停一下,审核不过哦,哈哈
在这里插入图片描述

在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Linux驱动开发(六)---树莓派配合硬件进行字符驱动开发 的相关文章

  • touch命令在一个目录下创建多个文件(不同名称)

    我想制作一个在 bash 中创建目录和文件结构的脚本 我尝试过这样的事情 mkdir p 1 2 touch 1 2 a b c a b c 应该是在一个命令或其他命令中创建的文件 但由于某种原因 结构是这样的 current folder
  • 操作系统什么时候清除进程的内存

    进程在某些操作系统上成功或异常终止 操作系统何时决定擦除分配给该进程的内存 数据 代码等 在退出时或当它想为新进程分配内存时 这个清除内存分配过程在所有操作系统 winXP Win7 linux Mac 上都相同吗 据我了解 页表具有该进程
  • 为 Qt 应用程序创建 Linux 安装

    我刚刚用 Qt Creator 制作了一个很棒的程序 我对自己很满意 如何将其从台式机移至笔记本电脑 那么 最好的方法是安装程序 对吗 对于 Ubuntu 这是一个 Debian 软件包 对吗 我怎么做 有人这样做过吗 他们可以分享 QT
  • 如何让“grep”从文件中读取模式?

    假设有一个很大的文本文件 我只想打印与某些模式不匹配的行 显然 我可以使用egrep v patter1 pattern2 pattern3 现在 如果所有这些模式都在一个文本文件中怎么办 最好的制作方法是什么egrep从文件中读取模式 g
  • C 程序从连接到系统的 USB 设备读取数据

    我正在尝试从连接到系统 USB 端口的 USB 设备 例如随身碟 获取数据 在这里 我可以打开设备文件并读取一些随机原始数据 但我想获取像 minicom teraterm 这样的数据 请让我知道我可以使用哪些方法和库来成功完成此操作以及如
  • Linux 桌面快捷方式和安装图标

    我需要添加什么到我的 spec文件来创建桌面快捷方式并在安装过程中为快捷方式分配一个图标 rpm 如果需要脚本 一个示例将非常有帮助 您在 Linux 下使用 desktop 文件作为图标 图标放置的位置取决于您使用的发行版和桌面环境 由于
  • 没有可用的符号表信息

    我正在测试第三方的库 它崩溃了 当我想查看崩溃的原因时 我的 gdb 告诉我没有可用的调试符号 Program received signal SIGSEGV Segmentation fault Switching to Thread 0
  • 在 /dev/input/eventX 中写入事件需要哪些命令?

    我正在开发一个android需要将触摸事件发送到 dev input eventX 的应用程序 我知道C执行此类操作的代码结构如下 struct input event struct timeval time unsigned short
  • arm-linux-gnueabi 编译器选项

    我在用 ARM Linux gnueabi gcc在 Linux 中为 ARM 处理器编译 C 程序 但是 我不确定它编译的默认 ARM 模式是什么 例如 对于 C 代码 test c unsigned int main return 0x
  • SONAR - 使用 Cobertura 测量代码覆盖率

    我正在使用声纳来测量代码质量 我不知道的一件事是使用 Cobertura 测量代码覆盖率的步骤 我按照以下步骤操作http cobertura sourceforge net anttaskreference html http cober
  • GCC 和 ld 找不到导出的符号...但它们在那里

    我有一个 C 库和一个 C 应用程序 尝试使用从该库导出的函数和类 该库构建良好 应用程序可以编译 但无法链接 我得到的错误遵循以下形式 app source file cpp text 0x2fdb 对 lib namespace Get
  • .NET Core 中的跨平台文件名处理

    如何处理文件名System IO以跨平台方式运行类以使其在 Windows 和 Linux 上运行 例如 我编写的代码在 Windows 上完美运行 但它不会在 Ubuntu Linux 上创建文件 var tempFilename Dat
  • 并行运行 make 时出错

    考虑以下制作 all a b a echo a exit 1 b echo b start sleep 1 echo b end 当运行它时make j2我收到以下输出 echo a echo b start a exit 1 b star
  • 在 Linux 中禁用历史记录 [关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 要在 Linux 环境中禁用历史记录 我执行了以下命令 export HISTFILESIZE 0 export HISTSIZE 0 u
  • QFileDialog::getSaveFileName 和默认的 selectedFilter

    我有 getSaveFileName 和一些过滤器 我希望当用户打开 保存 对话框时选择其中之一 Qt 文档说明如下 可以通过将 selectedFilter 设置为所需的值来选择默认过滤器 我尝试以下变体 QString selFilte
  • xsel -o 对于 OS X 等效项

    是否有一个等效的解决方案可以在 OS X 中抓取选定的文本 就像适用于 Linux 的 xsel o 一样 只需要当前的选择 这样我就可以在 shell 脚本中使用文本 干杯 埃里克 你也许可以安装xsel在 MacOS 上 更新 根据 A
  • 如何修复“iptables:没有该名称的链/目标/匹配”?

    我在我的 Linux 嵌入式系统上构建并安装了 iptables 如果我列出所有规则 则一切正常 iptables list Chain INPUT policy ACCEPT target prot opt source destinat
  • 修改linux下的路径

    虽然我认为我已经接近 Linux 专业人士 但显然我仍然是一个初学者 当我登录服务器时 我需要使用最新版本的R 统计软件 R 安装在 2 个地方 当我运行以下命令时 which R I get usr bin R 进而 R version
  • 在 Linux 上更快地分叉大型进程?

    在现代 Linux 上达到与 Linux 相同效果的最快 最好的方法是什么 fork execve combo 从一个大的过程 我的问题是进程分叉大约 500MByte 大 并且一个简单的基准测试只能从进程中实现约 50 个分叉 秒 比较最
  • awk 子串单个字符

    这是columns txt aaa bbb 3 ccc ddd 2 eee fff 1 3 3 g 3 hhh i jjj 3 kkk ll 3 mm nn oo 3 我可以找到第二列以 b 开头的行 awk if substr 2 1 1

随机推荐