9.2 Linux LED 驱动开发

一、Linux 下的 LED 驱动原理

  Linux 下的任何驱动,最后都是要配置相应的硬件寄存器。

1. 地址映射

  MMU 全称叫做 MemoryManage Unit,也就是内存管理单元。 现在的 Linux 支持无 MMU 处理器。MMU 主要完成的功能为:

  1、完成虚拟空间到物理空间的映射。

  2、内存保护,设置存储器的访问权限,设置虚拟存储空间的缓冲特性。

  虚拟空间到物理空间的映射其实就是 地址映射。 虚拟地址(VA,Virtual Address)、物理地址(PA, Physcical Address)。对于 32位的处理器来说,虚拟地址范围是 2^32=4GB,我们的开发板上有 1GB 的 DDR3,这 1GB 的内存就是物理内存,经过 MMU 可以将其映射到整个 4GB 的虚拟空间:

  Linux 内核启动的时候会初始化 MMU,设置好内存映射,设置好以后 CPU 访问的都是虚拟地址。比如 STM32MP157 的 PI0 引脚的端口模式寄存器 GPIOI_MODER 物理地址为0x5000A000。如果没有开启 MMU 的话直接向 0x5000A000 这个寄存器地址写入数据就可以配置 PI0 的引脚功能(输入、输出、复用或模拟等)。

  那有人会有疑惑,那为什么要开启MMU呢?开启 MMU 使得操作系统能够更好地管理内存资源、提供虚拟内存、实施内存保护和权限控制,并提供了更高效、安全、灵活的内存访问方式。总而言之,开启 MMU 的好处大大滴。

  现在开启了 MMU,并且设置了内存映射,因此就不能直接向 0x5000A000 这个地址写入数据了。我们必须得到 0x5000A000 这个物理地址在Linux 系统里面对应的虚拟地址,这里就涉及到了物理内存和虚拟内存之间的转换,需要用到
两个函数: ioremap 和 iounmap。 

① ioremap 函数

/** @description : 获取指定物理地址空间对应的虚拟地址空间* @param - res_cookie : 映射的物理起始地址* @param - size : 映射的内存空间大小* @return : __iomem 类型的指针,指向映射后的虚拟空间首地址* __iomen:可以修饰指针类型,将其标记为 I/O 内存指针, I/O 内存指针目的是告知编译器该指针指向的内存区域用于与 I/O 设备进行直接交互,需要采取特殊的读写方式和对齐规则*/
void __iomem *ioremap(resource_size_t res_cookie, size_t size)
{return arch_ioremap_caller(res_cookie, size, MT_DEVICE, __builtin_return_address(0));
}

  如果需要 STM32MP157-ATK 的 GPIOI_MODER 寄存器对应的虚拟地址 ,代码如下:

#define GPIOI_MODER        (0X5000A000)
static void __iomen        *GPIO_MODER_PI;
GPIO_MODER_PI = ioremap(GPIOI_MODER, 4);

  宏 GPIOI_MODER 是寄存器物理地址, GPIO_MODER_PI 是映射后的虚拟地址。对于 STMP32MP157 来说一个寄存器是 4 字节(32 位),因此映射的内存长度为 4。映射完成以后直接对 GPIO_MODER_PI 进行读写操作即可。 

② iounmap 函数

  卸载驱动的时候需要使用 iounmap 函数释放掉 ioremap 函数所做的映射:

/** @description : 释放 ioremap 所做的映射* @param - addr : 取消映射的虚拟地址空间首地址*/
void iounmap (volatile void __iomem *addr);

  比如现在要卸载 GPIO_MODER_PI 寄存器的地址映射:

iounmap(GPIO_MODER_PI);

2. I/O 内存访问函数

  I/O 是输入/输出的意思, 这里涉及到两个概念: I/O 端口和 I/O 内存。当外部寄存器或内存映射到 IO 空间时,称为 I/O 端口。当外部寄存器或内存映射到内存空间时,称为 I/O 内存。 但 ARM 空间只有 I/O 内存。Linux 内核建议使用一组操作函数来对映射后的内存进行读写操作。 

① 读操作函数

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 就是要读取写内存地址,返回值就是读取到的数据。*/

②写操作函数

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 是要写入的地址。*/

二、硬件原理图分析

  LED0 接到了 PI0 上, PI0 就是 GPIOI 组的第 0 个引脚,当 PI0 输出低电平(0)的时候发光二极管 LED0 就会导通点亮,当 PI0 输出高电平(1)的时候发光二极管LED0 不会导通,因此 LED0 也就不会点亮。所以 LED0 的亮灭取决于 PI0 的输出电平,输出 0 就亮,输出 1 就灭。 

三、实验程序编写

1. 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>#define LED_MAJOR 200 /* 主设备号 */
#define LED_NAME "led" /* 设备名字 */#define LEDOFF 0 /* 关灯 */
#define LEDON 1 /* 开灯 *//* 寄存器物理地址 */
#define PERIPH_BASE (0x40000000)
#define MPU_AHB4_PERIPH_BASE (PERIPH_BASE + 0x10000000)
#define RCC_BASE (MPU_AHB4_PERIPH_BASE + 0x0000)
#define RCC_MP_AHB4ENSETR (RCC_BASE + 0XA28)
#define GPIOI_BASE (MPU_AHB4_PERIPH_BASE + 0xA000)
#define GPIOI_MODER (GPIOI_BASE + 0x0000)
#define GPIOI_OTYPER (GPIOI_BASE + 0x0004)
#define GPIOI_OSPEEDR (GPIOI_BASE + 0x0008)
#define GPIOI_PUPDR (GPIOI_BASE + 0x000C)
#define GPIOI_BSRR (GPIOI_BASE + 0x0018)/* 映射后的寄存器虚拟地址指针 */
static void __iomem *MPU_AHB4_PERIPH_RCC_PI;
static void __iomem *GPIOI_MODER_PI;
static void __iomem *GPIOI_OTYPER_PI;
static void __iomem *GPIOI_OSPEEDR_PI;
static void __iomem *GPIOI_PUPDR_PI;
static void __iomem *GPIOI_BSRR_PI;void led_switch(u8 sta)
{u32 val = 0;if(sta == LEDON) {val = readl(GPIOI_BSRR_PI);val |= (1 << 16);writel(val, GPIOI_BSRR_PI);}else if(sta == LEDOFF) {val = readl(GPIOI_BSRR_PI);val |= (1 << 0);writel(val, GPIOI_BSRR_PI);}
}void led_unmap(void)
{iounmap(MPU_AHB4_PERIPH_RCC_PI);iounmap(GPIOI_MODER_PI);iounmap(GPIOI_OTYPER_PI);iounmap(GPIOI_OSPEEDR_PI);iounmap(GPIOI_PUPDR_PI);iounmap(GPIOI_BSRR_PI);
}static int led_open(struct inode *inode, struct file *filp)
{return 0;
}static ssize_t led_read(struct file *filp, char __user *buf,size_t cnt, loff_t *offt)
{return 0;
}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);} else if(ledstat == LEDOFF) {led_switch(LEDOFF);}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,
};static int __init led_init(void)
{int retvalue = 0;u32 val = 0;MPU_AHB4_PERIPH_RCC_PI = ioremap(RCC_MP_AHB4ENSETR, 4);GPIOI_MODER_PI = ioremap(GPIOI_MODER, 4);GPIOI_OTYPER_PI = ioremap(GPIOI_OTYPER, 4);GPIOI_OSPEEDR_PI = ioremap(GPIOI_OSPEEDR, 4);GPIOI_PUPDR_PI = ioremap(GPIOI_PUPDR, 4);GPIOI_BSRR_PI = ioremap(GPIOI_BSRR, 4);val = readl(MPU_AHB4_PERIPH_RCC_PI);val &= ~(0X1 << 8);val |= (0X1 << 8);writel(val, MPU_AHB4_PERIPH_RCC_PI);val = readl(GPIOI_MODER_PI);val &= ~(0X3 << 0);val |= (0X1 << 0);writel(val, GPIOI_MODER_PI);val = readl(GPIOI_OTYPER_PI);val &= ~(0X1 << 0);writel(val, GPIOI_OTYPER_PI);val = readl(GPIOI_OSPEEDR_PI);val &= ~(0X3 << 0);val |= (0x2 << 0);writel(val, GPIOI_OSPEEDR_PI);val = readl(GPIOI_PUPDR_PI);val &= ~(0X3 << 0);val |= (0x1 << 0);writel(val,GPIOI_PUPDR_PI);val = readl(GPIOI_BSRR_PI);val |= (0x1 << 0);writel(val, GPIOI_BSRR_PI);retvalue = register_chrdev(LED_MAJOR, LED_NAME, &led_fops);if(retvalue < 0){printk("register chrdev failed!\r\n");goto fail_map;}return 0;fail_map:led_unmap();return -EIO;
}static void __exit led_exit(void)
{led_unmap();unregister_chrdev(LED_MAJOR, LED_NAME);
}module_init(led_init);
module_exit(led_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("LXS");
MODULE_INFO(intree, "Y");

2. 测试 APP

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"#define LEDOFF 0
#define LEDON 1int main(int argc, char *argv[])
{int fd, retvalue;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", argv[1]);return -1;}databuf[0] = atoi(argv[2]);retvalue = write(fd, databuf, sizeof(databuf));if(retvalue < 0){printf("LED Control Failed!\r\n");close(fd);return -1;}retvalue = close(fd);if(retvalue < 0){printf("file %s close failed!\r\n", argv[1]);return -1;}return 0;
}

三、运行测试

1. 编译驱动程序

  依然是利用 Makefile 文件把 chrdevbase.c 编译为 chrdevbase.ko 模块:

KERNELDIR := /home/alientek/linux/atk-mpl/linux/my_linux/linux-5.4.31	# Linux内核源码路径
CURRENT_PATH := $(shell pwd)		# 获取当前所处路径
obj-m := led.o		build: kernel_moduleskernel_modules:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

  之后在 /linux/atk-mpl/Drivers/2_led 路径下输入:

make -j32

2. 编译测试 APP

  因为要在 ARM 上面运行,所以要用交叉编译器:

arm-none-linux-gnueabihf-gcc ledApp.c -o ledApp

3. 运行测试

  将 led.ko 和 ledApp 拷贝到 /linux/nfs/rootfs/lib/modules/5.4.31 路径下:

sudo cp ledApp led.ko /home/alientek/linux/nfs/rootfs/lib/modules/5.4.31/ -f

  加载 led.ko 驱动模块:ls

depmod
modprobe led    # 加载驱动
lsmod           # 查看加载的驱动

  驱动加载成功后创建 "/dev/led" 设备节点:

mknod /dev/led c 200 0

  输入以下命令测试 LED:

# 打开 LED
./ledApp /dev/led 1# 关闭 LED
./ledApp /dev/led 0

  如果开发版的红色 LED 能打开和关闭说明测试成功。卸载驱动:

rmmod led.ko

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

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

相关文章

java-sec-code的xss

java-sec-code 用于学习java漏洞代码 环境部署 直接在idea中使用git 运行即可 RequestMapping("/reflect") ResponseBody public static String reflect(String xss) {return xss;}当用户访问到/reflect URL地址时&#xff0c;程序会自动调用reflect方法&#xff0c…

『踩坑记录』IDEA Spring initialzr新建Spring项目不能选择jdk8的解决方法

问题描述 Spring initializr新建Spring项目不能选低版本java 解决方法 默认官方start.spring.io已不支持自动生成低版本jkd的Spring项目&#xff0c;自定义用阿里云的starter即可 用阿里云的就能支持低版本jdk了 完 欢迎关注我的CSDN博客 &#xff1a;Ho1aAs 版权属于&a…

Tekton 基于 gitlab 触发流水线

Tekton 基于 gitlab 触发流水线 Tekton EventListener 在8080端口监听事件&#xff0c;Gitlab 提交代码产生push 事件&#xff0c;gitlab webhook触发tekton流水线执行。 前置要求&#xff1a; kubernetes集群中已部署 tekton pipeline、tekton triggers以及tekton dashboa…

SpringMVC的文件上传、多文件上传

概念&#xff1a;上传/下载应用 对于上传功能&#xff0c;我们在项目中是经常会用到的&#xff0c;比如用户注册的时候&#xff0c;上传用户头像&#xff0c;这个时候就会使用到上传的功能。而对于下载&#xff0c;使用场景也很常见&#xff0c;比如我们项目中有个使用说明是是…

【Spring】04 国际化

文章目录 1. 定义2. Spring 的支持1&#xff09; MessageSource接口2&#xff09; ResourceBundleMessageSource 3. 配置国际化1&#xff09;配置MessageSource Bean2&#xff09;创建资源文件3&#xff09;在Bean中使用国际化消息 4. 使用占位符和参数结语 Spring 为我们提供了…

遇到运维故障,有没有排查和解决故障的正确流程?

稳定是偶然&#xff0c;异常才是常态&#xff0c;用来标注IT运维工作再适合不过。 因为对于IT运维来说&#xff0c;工作最常遇到的就是不稳定性带来的各种故障&#xff0c;经常围绕发现故障、响应故障、定位故障、恢复故障这四大步。 故障处理是最心跳的事情&#xff0c;没有…

XETUX软件 dynamiccontent.properties.xhtml RCE漏洞复现

0x01 产品简介 XETUX 是一个全面的解决方案,包括一套安全、强大和可监控的软件程序,专为自动控制餐厅和零售而设计和开发。 0x02 漏洞概述 XETUX 存在代码执行漏洞,攻击者可通过 dynamiccontent.properties.xhtml 执行任意代码获取服务器权限。 0x03 复现环境 FOFA:ti…

SQL进阶理论篇(四):索引的结构原理(B树与B+树)

文章目录 简介如何评价索引的数据结构设计好坏二叉树的局限性什么是B树什么是B树总结参考文献 简介 我们在上一节中说过&#xff0c;索引其实是一种数据结构&#xff0c;那它到底是一种什么样的数据结构呢&#xff1f;本节将简单介绍一下几个问题&#xff1a; 什么样的数据结…

<JavaEE> 网络编程 -- 网络通信基础(协议和协议分层、数据封装和分用)

目录 一、IP地址 1&#xff09;IP地址的概念 2&#xff09;IP地址的格式 二、端口号 1&#xff09;端口号的概念 2&#xff09;端口号的格式 3&#xff09;什么是知名端口号&#xff1f; 三、协议 1&#xff09;协议的概念 2&#xff09;协议的作用 3&#xff09;TC…

[笔记] iperf3.1.3源码下载与交叉编译

由于需要测试一款40G网卡&#xff0c;下载了 iperf3.1.3 用于性能测试。 iperf3.1.3 源码下载 可以在 iperf 官网 下载源代码&#xff1a; 交叉编译 需要运行在 aarch64 linux 环境下&#xff0c;所以需要交叉编译。 进入iperf3 目录下&#xff0c;运行 ./configure 脚本…

ssm基于HTML5的OA办公系统论文

基于HTML5的OA办公系统 摘要 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;作为一个一般的企业都开始注重与自己的信息展示平台&#xff0c;实现基于HTML5的OA办公系统在技术上已成熟。本文介绍了基于HTML5的OA办公系统的开发全过程。通过分析企业对于博客网站的需…

打工人副业变现秘籍,某多/某手变现底层引擎-Stable Diffusion 模特假人换服装、换背景、换真人

给固定人物换背景或者换服装,需要用到一个Stable Diffusion扩展插件,就是sd-webui-segment-anything。 sd-webui-segment-anything 不仅可以做到抠图的效果,也能实现之多蒙版的效果。 什么是蒙版 图片蒙版是一种用于调节图像修改程度以及进行局部调整的工具。它通常分为四种…

安全算法(二):共享密钥加密、公开密钥加密、混合加密和迪菲-赫尔曼密钥交换

安全算法&#xff08;二&#xff09;&#xff1a;共享密钥加密、公开密钥加密、混合加密和迪菲-赫尔曼密钥交换 本章介绍了共享密钥加密、公开密钥加密&#xff0c;和两种加密方法混合使用的混合加密方法&#xff1b;最后介绍了迪菲-赫尔曼密钥交换。 加密数据的方法可以分为…

16--常用类和基础API--06

1、包装类 1.1 包装类概述 Java提供了两个类型系统&#xff0c;基本类型与引用类型&#xff0c;使用基本类型在于效率&#xff0c;然而很多情况&#xff0c;会创建对象使用&#xff0c;因为对象可以做更多的功能&#xff0c;如果想要我们的基本类型像对象一样操作&#xff0c…

Gitlab基础篇: Gitlab docker 安装部署、Gitlab 设置账号密码

文章目录 1、环境准备2、配置1)、初始化2)、修改gitlab配置文件3)、修改docker配置的gitlab默认端口 gitlab进阶配置gitlab 设置账号密码 1、环境准备 安装docker gitlab前确保docker环境&#xff0c;如果没有搭建docker请查阅“Linux docker 安装文档” docker 下载 gitlab容…

STM32 PVD掉电检测功能的使用方法

STM32 PVD掉电检测功能的使用方法 前言 在实际应用场景中&#xff0c;可能会出现设备电源电压异常下降或掉电的情况&#xff0c;因此&#xff0c;有时候需要检测设备是否掉电&#xff0c;或者在设备掉电的瞬间做一些紧急关机处理&#xff0c;比如保存重要的用户数据&#xff…

【C语言】超详解strncpystrncatstrncmpstrerrorperror的使⽤和模拟实现

&#x1f308;write in front :&#x1f50d;个人主页 &#xff1a; 啊森要自信的主页 ✏️真正相信奇迹的家伙&#xff0c;本身和奇迹一样了不起啊&#xff01; 欢迎大家关注&#x1f50d;点赞&#x1f44d;收藏⭐️留言&#x1f4dd;>希望看完我的文章对你有小小的帮助&am…

jmeter,跨线程调用cookie

结构目录 一、线程组1 1、创建登录的【HTTP请求】&#xff0c;并配置接口所需参数&#xff1b; 2、创建【正则表达式提取器】&#xff0c;用正则表达式提取cookie字段&#xff1b; 3、创建【调试取样器】&#xff0c;便于观察第2步提取出的数据&#xff1b; 4、创建【BeanSh…

【Jenkins】节点 node、凭据 credentials、任务 job

一、节点 node Jenkins在安装并初始化完成后&#xff0c;会有一个主节点&#xff08;Master Node&#xff09;&#xff0c;默认情况下主节点可以同时运行的任务数是2&#xff0c;可以在节点配置中修改&#xff08;系统管理/节点和云管理&#xff09;。 Jenkins中的节点&#…

nodejs+vue+微信小程序+python+PHP血液中心管理平台的设计与实现-计算机毕业设计推荐

实现采血的完整功能&#xff0c;系统用户主要分为两类&#xff0c;一类是管理员&#xff0c;一类是采血工作人员。管理员主要对采血工作人员以及血库进行管理。派发账号给员工作为采血工作人员&#xff0c;对血库的出库入库进行信息化管理。采血工作人员主要完成采血工作。通过…