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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

香港科技大学数据建模(MSc DDM)硕士学位项目(2024年秋季入学)招生宣讲会-四川大学专场

时间&#xff1a;2023 年 12 月 26 日&#xff08;周二&#xff09; 14:30 地点&#xff1a;四川大学望江校区基础教学楼 C 座 102 嘉宾教授&#xff1a;潘鼎 教授 项目旨在培养科学或工程背景的学员从数据中提取信息的数据建模能力&#xff0c;训练其拥有优秀的解难和逻辑思…

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

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

谷歌浏览器怎么关闭自动更新?

文章目录 一、方式一 谷歌浏览器安装完成后&#xff0c;每天都会自动更新到最新的版本&#xff0c;但是对于有些程序的驱动&#xff0c;浏览器一更新就不能自动启动浏览器&#xff0c;会给我们带来很多困扰。下面我们介绍怎么将谷歌浏览器自动更新关闭&#xff0c;如果需要更新…

# 和 $ 的区别②

上节博客说了使用 # 的时候,如果参数为 String ,会自动加上单引号 但是当参数为String 类型的时候,也有不需要加单引号的情况,这时候用 # 那就会出问题 比如根据 升序(asc) 或者 降序(desc) 查找的时候,加了单引号那就会报错 这个时候我们就只能使用 $ 如果看不懂代码,就去…

Android Studio实现俄罗斯方块

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

Rask AI引领革新,推出多扬声器口型同步技术,打造本地化内容新纪元

“ Rask AI是一个先进的AI驱动视频和音频本地化工具&#xff0c;旨在帮助内容创作者和公司快速、高效地将他们的视频转换成60多种语言。通过不断创新和改进产品功能&#xff0c;Rask AI正塑造着未来媒体产业的发展趋势。 ” 在多语种内容创作的新时代&#xff0c;Rask AI不断突…

spring 笔记六 SpringMVC 获得请求数据

文章目录 SpringMVC 获得请求数据获得请求参数获得基本类型参数获得POJO类型参数获得数组类型参数获得集合类型参数请求数据乱码问题参数绑定注解requestParam获得Restful风格的参数获得Restful风格的参数自定义类型转换器获得Servlet相关API获得请求头RequestHeaderCookieValu…

CMS—评论设计

一、需求分析 1.1、常见行为 1.敏感词过滤 2.新增评论&#xff08;作品下、评论下&#xff09; 3.删除评论&#xff08;作品作者、上级评论者、本级作者&#xff09; 4.上级评论删除关联下级评论 5.逻辑状态变更&#xff08;上线、下线、废弃...&#xff09; 6.上逻辑状态变更…

Mac部署Odoo环境-Odoo本地环境部署

Odoo本地环境部署 安装Python安装Homebrew安装依赖brew install libxmlsec1 Python运行环境Pycharm示例配置 Mac部署Odoo环境-Odoo本地环境部署 安装Python 新机&#xff0c;若系统没有预装Python&#xff0c;则安装需要版本的Python 点击查询Python官网下载 安装Homebrew 一…

solidity 特性导致的漏洞

目录 1、默认可见性 2、浮点数精度缺失 3、错误的构造函数 4、自毁函数 5、未初始化指针-状态变量覆盖 1、默认可见性 Solidity 的函数和状态变量有四种可见性&#xff1a;external、public、internal、private。函数可见性默认为 public&#xff0c;状态变量可见性默认为…

RS485转WiFi工业路由器在冷链物流温度监控中的应用

随着物联网技术的不断发展和应用&#xff0c;冷链物流行业也迎来了新的机遇和挑战。在冷链物流中&#xff0c;对温度监控的要求尤为重要&#xff0c;因为温度是保证货物质量和安全的关键因素之一。而RS485转WiFi工业路由器则成为了实现高效、可靠的温度监控系统的重要组成部分。…

Linux ed命令教程:如何使用ed命令编辑文本文件(附案例详解和注意事项)

Linux ed命令介绍 ed命令是Linux中的一个简单文本编辑器。它是一种基于行的文本编辑器&#xff0c;用于创建、修改和操作文本文件。它是Unix中最早的编辑器&#xff0c;后来被vi和emacs文本编辑器所取代。 Linux ed命令适用的Linux版本 ed命令在大多数Linux发行版中都可以使…

群晖(Synology)云备份的方案是什么

群晖云备份方案就是在本地的 NAS 如果出现问题&#xff0c;或者必须需要重做整列的时候&#xff0c;保证数据不丢失。 当然&#xff0c;这些是针对有价值的数据&#xff0c;如果只是电影或者不是自己的拍摄素材文件&#xff0c;其实可以不使用云备份方案&#xff0c;因为毕竟云…

hive常用SQL函数及案例

1 函数简介 Hive会将常用的逻辑封装成函数给用户进行使用&#xff0c;类似于Java中的函数。 好处&#xff1a;避免用户反复写逻辑&#xff0c;可以直接拿来使用。 重点&#xff1a;用户需要知道函数叫什么&#xff0c;能做什么。 Hive提供了大量的内置函数&#xff0c;按照其特…

软件测评中心 ▏科技项目验收测试流程和注意事项简析

科技项目验收测试是指对已开发完成的科技项目进行测试和评估&#xff0c;以确认其达到预期的功能和性能要求&#xff0c;保证项目的质量和可靠性。 一、科技项目验收测试的流程一般包括以下几个阶段&#xff1a;   1、需求分析和测试计划&#xff1a;在开始测试前&#xff0…

RK3568平台(网络篇) 有线网络基本概念及测试手法

一.什么是交换机&#xff1f; 交换机是一种用于电(光)信号转发的网络设备。它可以为接入交换机的任意两个网络节点提供独享的电信号通路。最常见的交换机是以太网交换机。交换机工作于OSI参考模型的第二层&#xff0c;即数据链路层。交换机拥有一条高带宽的背部总线和内部交换…

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

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

LED恒流调节器FP7125,应用LED街道照明、调光电源、汽车大灯、T5T8日光灯

目录 一、FP7125概述 二、FP7125功能 三、应用领域 近年来&#xff0c;随着人们环保意识的不断增强&#xff0c;LED照明产品逐渐成为照明行业的主流。而作为LED照明产品中的重要配件&#xff0c;LED恒流调节器FP7125的出现为LED照明带来了全新的发展机遇。 一、FP7125概述 FP…

HarmonyOS(十三)——详解自定义组件的生命周期

前言 自定义组件的生命周期回调函数用于通知用户该自定义组件的生命周期&#xff0c;这些回调函数是私有的&#xff0c;在运行时由开发框架在特定的时间进行调用&#xff0c;不能从应用程序中手动调用这些回调函数。 下图展示的是被Entry装饰的组件生命周期&#xff1a; 今…

mysql 与mssql 命令有那些区别

use databasename 进入指定数据库名 命令一致 select databse() 查询当前进入数据库的名 mssql无法使用&#xff0c;mysql正常 mssql 暂无 C知道介绍 以下是MySQL和MSSQL命令的一些区别&#xff1a; 1. 连接数据库的命令不同&#xff1a; - MySQL&#xff1a;…