9.2 Linux LED 驱动开发

news2024/9/21 18:59:14

一、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 1

int 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_modules

kernel_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.coloradmin.cn/o/1311923.html

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

相关文章

系统登页面——大屏系统深蓝色主题

加了线上验证码校验还有密码账号校验。其他的资料都放在文章末尾了。 <template xmlns"http://www.w3.org/1999/html"><div class"login"><img :src"imgBg" class"login_bg" width"100%" height"100%&q…

Sql标准梳理

SQL&#xff08;Structured Query Language&#xff09;是一种用于管理关系型数据库管理系统&#xff08;RDBMS&#xff09;的标准化语言。SQL标准由国际标准化组织&#xff08;ISO&#xff09;和美国国家标准化组织&#xff08;ANSI&#xff09;制定和维护&#xff0c;旨在提供…

利用高级 CSPM 应对现代攻击

混合云和多云环境的快速增长造成了跨架构的复杂性&#xff0c;使得人们很难清楚、完整地了解技术堆栈中的各种平台。最近云攻击和破坏的激增引起了人们对团队应如何有效地保护和运行云中的应用程序的关注。 因为错误配置是云环境中安全威胁的首位&#xff0c;并且是基于云的攻…

Android--Jetpack--Navigation详解

须知少日拏云志&#xff0c;曾许人间第一流 一&#xff0c;定义 Navigation 翻译成中文就是导航的意思。它是谷歌推出的Jetpack的一员&#xff0c;其目的主要就是来管理页面的切换和导航。 Activity 嵌套多个 Fragment 的 UI 架构模式已经非常普遍&#xff0c;但是对 Fragmen…

Kubernetes 容器编排 -- 1

前言 知识扩展 早在 2015 年 5 月&#xff0c;Kubernetes 在 Google 上的搜索热度就已经超过了 Mesos 和 Docker Swarm&#xff0c;从那儿之后更是一路飙升&#xff0c;将对手甩开了十几条街,容器编排引擎领域的三足鼎立时代结束。 目前&#xff0c;AWS、Azure、Google、阿里…

python学习1

大家好&#xff0c;这里是七七&#xff0c;今天开始又新开一个专栏&#xff0c;Python学习。这次思考了些许&#xff0c;准备用例子来学习&#xff0c;而不是只通过一大堆道理和书本来学习了。啊对&#xff0c;这次是从0开始学习&#xff0c;因此大佬不用看本文了&#xff0c;小…

旅游景区文旅地产如何通过数字人开启数字营销?

随着元宇宙的发展&#xff0c;为虚实相生的营销带来更多的可能性。基于虚拟世界对于现实世界的模仿&#xff0c;通过构建沉浸式数字体验&#xff0c;增强现实生活的数字体验&#xff0c;强调实现真实体验的数字化&#xff0c;让品牌结合数字人开启数字化营销。 *图片源于网络 …

Qt图像处理-Qt中配置OpenCV打开本地图片

本文讲解Qt中配置OpenCV过程并用实例展示如何使用OpenCV打开图片(windows环境下) 一、下载OpenCv 本文使用版本OpenCV-MinGW-Build-OpenCV-3.4.5 下载地址: https://codeload.github.com/huihut/OpenCV-MinGW-Build/zip/refs/heads/OpenCV-3.4.5 点击Code-local-Downlo…

【VRTK】【VR开发】【Unity】15-远程抓取

课程配套学习资源下载 https://download.csdn.net/download/weixin_41697242/88485426?spm=1001.2014.3001.5503 【背景】 之前的篇章介绍了如何实现直接抓取,本篇介绍另一种抓取方式-远程抓取。 【远程抓取的先决条件】 要让远程抓取起作用,需要先设置oculus提供的手部…

架构设计系列之常见架构(一)

本部分对常见架构进行简单的说明。 一、三层架构之经典 MVC 经典的 MVC 架构&#xff08;Model-View-Controller&#xff09;架构是软件系统架构设计中的经典&#xff0c;它将应用程序分为三个主要部分&#xff1a; 模型&#xff08;Model&#xff09;视图&#xff08;View&…

内存问题(一)——内存概述

一、内存泄漏&#xff08;Memory Leak&#xff09; 是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放&#xff0c;造成系统内存的浪费&#xff0c;导致程序运行速度减慢甚至系统崩溃等严重后果。 二、一般内存泄露的方式 常发性内存泄漏&#xff1a;发生内存泄…

Java实现快速排序及其动图演示

快速排序&#xff08;Quicksort&#xff09;是一种基于分治思想的排序算法。它通过选择一个基准元素&#xff0c;将数组分为两个子数组&#xff0c;其中一个子数组的所有元素都小于基准元素&#xff0c;另一个子数组的所有元素都大于基准元素&#xff0c;然后递归地对这两个子数…

C_11练习题答案

一、单项选择题(本大题共20小题,每小题2分,共40分。在每小题给出的四个备选项中,选出一个正确的答案,并将所选项前的字母填写在答题纸的相应位置上。) 以下叙述中正确的是(C)A.C语言不是一种高级语言 B.C语言不用编译就能被计算机执行 C.C语言能够直接访问物理地址和进行位…

Process On在线绘制流程图

目录 一.ProcessOn 1.1.介绍 1.2.直接网上使用 二.绘制门诊流程图 三.绘制住院流程图 四.绘制药库采购入库流程图 五.绘制OA会议流程图 今天就到这里了哦!!!希望能帮到你哦&#xff01;&#xff01;&#xff01; 一.ProcessOn 1.1.介绍 ProcessOn&#xff08;流程&#…

【强化学习-读书笔记】多臂赌博机 Multi-armed bandit

参考 Reinforcement Learning, Second Edition An Introduction By Richard S. Sutton and Andrew G. Barto强化学习与监督学习 强化学习与其他机器学习方法最大的不同&#xff0c;就在于前者的训练信号是用来评估&#xff08;而不是指导&#xff09;给定动作的好坏的。 …

Windows中使用pthread线程库

由于时间成本&#xff0c;不想使用Windows线程API&#xff0c;因此想用pthread线程库&#xff1b;但pthread是Linux默认的POSIX线程库&#xff0c;Windows中并不自带&#xff0c;需要自己配置。 因为pthread遵循POSIX标准&#xff0c;因此其在Windows中使用应该和Linux中大同小…

JAVA:深入探讨Map的多种遍历方式

1、简述 在现代编程中&#xff0c;Map&#xff08;映射&#xff09;是一种常见的数据结构&#xff0c;用于存储键-值对。在许多编程语言中&#xff0c;Map提供了灵活的数据组织方式&#xff0c;但为了充分发挥其功能&#xff0c;我们需要了解多种遍历方式。本文将深入探讨Map的…

国际刑警组织推出新的生物识别系统

2023 年 11 月 29 日&#xff0c;国际刑警组织发布了一份有关创建生物识别工具的新闻稿&#xff0c;至少在意大利&#xff0c;该工具似乎已经陷入沉默&#xff0c;但让我们看看为什么我们会对这个东西感兴趣。 国际刑警组织的新闻稿用了整整一段时间来讨论与隐私相关的问题&am…

【sqli靶场】第六关和第七关通关思路

目录 前言 一、sqli靶场第六关 1.1 判断注入类型 1.2 观察报错 1.3 使用extractvalue函数报错 1.4 爆出数据库中的表名 二、sqli靶场第七关 1.1 判断注入类型 1.2 判断数据表中的字段数 1.3 提示 1.4 构造poc爆库名 1.5 构造poc爆表名 1.6 构造poc爆字段名 1.7 构造poc获取账…

Android Studio实现俄罗斯方块

文章目录 一、项目概述二、开发环境三、详细设计3.1 CacheUtils类3.2 BlockAdapter类3.3 CommonAdapter类3.4 SelectActivity3.5 MainActivity 四、运行演示五、项目总结 一、项目概述 俄罗斯方块是一种经典的电子游戏&#xff0c;最早由俄罗斯人Alexey Pajitnov在1984年创建。…