当前位置: 首页 > news >正文

嵌入式Linux驱动开发 - 新字符设备LED驱动

嵌入式Linux驱动开发 - 新字符设备LED驱动

一、项目概述

本项目实现了一个基于IMX6ULL开发板的LED字符设备驱动程序。驱动程序控制开发板上的GPIO引脚来实现LED的开关控制,配套的应用程序可以向驱动发送控制指令。

二、驱动程序分析 (newchrled.c)

1. 头文件与宏定义

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <asm/io.h>
#include <linux/uaccess.h>
#include <asm/io.h>
#include <linux/cdev.h>
#include <linux/device.h>#define LED_NAME "newchrled"
#define LED_MAJOR 200  // 主设备号
#define LEDON 1        // LED开启状态
#define LEDOFF 0       // LED关闭状态

2. 寄存器地址定义

#define CCM_CGR1_BASE 0x020C406C        // 时钟控制寄存器
#define SW_MUX_GPIO1_IO03_BASE 0x020E0068  // 复用选择寄存器
#define SW_PAD_GPIO1_IO03_BASE 0x020E02F4  // PAD配置寄存器
#define GPIO1_DR_BASE 0x0209C000        // GPIO数据寄存器
#define GPIO1_GDIR_BASE 0x0209C004      // GPIO方向寄存器

3. 设备结构体定义

struct newchrled_dev
{dev_t devid;          // 设备号struct cdev cdev;     // cdev结构体struct class *class;  // 类struct device *device;// 设备int major;            // 主设备号int minor;            // 次设备号
};
struct newchrled_dev newchrled; /* led设备 */

4. 硬件操作函数

led_switch - LED开关控制函数
void led_switch(int sta)
{u32 val = 0;if (sta == LEDON){val = readl(GPIO1_DR);val &= ~(1 << 3);  // 清除第3位writel(val, GPIO1_DR);}else if (sta == LEDOFF){val = readl(GPIO1_DR);val |= (1 << 3);   // 设置第3位writel(val, GPIO1_DR);}
}
led_write - 写操作函数
static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{int retvalue;unsigned char databuf[1];retvalue = copy_from_user(databuf, buf, cnt);if (retvalue < 0){printk("kernel write failed!\r\n");return -EFAULT;}led_switch(databuf[0]);return 0;
}
led_read - 读操作函数
static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{return 0;
}
文件操作结构体
static struct file_operations led_fops = {.owner = THIS_MODULE,.write = led_write,.read = led_read,.open = led_open,.release = led_release,
};

5. 驱动初始化与退出

初始化函数
static int __init led_init(void)
{u32 val = 0;// 映射寄存器物理地址到内核虚拟地址CCM_CGR1 = ioremap(CCM_CGR1_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);// 配置时钟val = readl(CCM_CGR1);val &= ~(3 << 26); // 清除时钟配置val |= (3 << 26);  // 使能GPIO1时钟writel(val, CCM_CGR1);// 配置GPIO复用writel(0x5, SW_MUX_GPIO1_IO03);    // 设置为GPIO模式writel(0x10B0, SW_PAD_GPIO1_IO03); // 设置PAD配置// 配置GPIO方向val = readl(GPIO1_GDIR);val &= ~(1 << 3); // 清除方向配置val |= (1 << 3);  // 设置为输出writel(val, GPIO1_GDIR);// 初始化LED状态val = readl(GPIO1_DR);val |= (1 << 3); // 设置为高电平(LED熄灭)writel(val, GPIO1_DR);// 注册设备号if (newchrled.major){newchrled.devid = MKDEV(newchrled.major, 0);register_chrdev_region(newchrled.devid, 1, LED_NAME);}else{alloc_chrdev_region(&newchrled.devid, 0, 1, LED_NAME);newchrled.major = MAJOR(newchrled.devid);newchrled.minor = MINOR(newchrled.devid);}// 初始化cdevnewchrled.cdev.owner = THIS_MODULE;cdev_init(&newchrled.cdev, &led_fops);cdev_add(&newchrled.cdev, newchrled.devid, 1);// 创建类和设备newchrled.class = class_create(THIS_MODULE, LED_NAME);if (IS_ERR(newchrled.class)){printk(KERN_ERR "Failed to create class\n");return PTR_ERR(newchrled.class);}newchrled.device = device_create(newchrled.class, NULL, newchrled.devid, NULL, LED_NAME);if (IS_ERR(newchrled.device)){printk(KERN_ERR "Failed to create device\n");class_destroy(newchrled.class);return PTR_ERR(newchrled.device);}return 0;
}
退出函数
static void __exit led_exit(void)
{// 取消地址映射iounmap(CCM_CGR1);iounmap(SW_MUX_GPIO1_IO03);iounmap(SW_PAD_GPIO1_IO03);iounmap(GPIO1_DR);iounmap(GPIO1_GDIR);// 删除cdevcdev_del(&newchrled.cdev);// 销毁设备和类device_destroy(newchrled.class, newchrled.devid);class_destroy(newchrled.class);// 注销设备号unregister_chrdev_region(newchrled.devid, 1);
}

6. 模块注册

module_init(led_init);
module_exit(led_exit);

7. 模块信息

MODULE_LICENSE("GPL");
MODULE_AUTHOR("alentek");

三、应用程序分析 (newchrledAPP.c)

1. 程序功能

该应用程序用于控制LED设备,通过命令行参数接收设备文件路径和LED状态参数。

2. 主要代码

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>int main(int argc, char *argv[])
{// 参数检查if (argc != 3)  {fprintf(stderr, "Usage: %s <led_device> <0|1>\n", argv[0]);return -1;}char* filename;unsigned char databuf[1];filename = argv[1];databuf[0] = atoi(argv[2]);int fd = 0;int ret = 0;// 打开设备文件fd = open(filename, O_RDWR);if (fd < 0){perror("open led device error");return -1;}// 写入数据ret = write(fd, databuf, 1);if (ret < 0){perror("write led device error");close(fd);return -1;}// 关闭文件close(fd);return 0;
}

四、Makefile分析

KERNERDIR := /home/ubuntu2004/linux/IMX6ULL/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga
CURRENTDIR := $(shell pwd)obj-m := newchrled.obuild : kernel_moduleskernel_modules:$(MAKE) -C $(KERNERDIR) M=$(CURRENTDIR) modulesclean:$(MAKE) -C $(KERNERDIR) M=$(CURRENTDIR) clean

1. 变量定义

  • KERNERDIR: 内核源码目录
  • CURRENTDIR: 当前工作目录
  • obj-m: 指定编译成内核模块

2. 编译目标

  • build: 编译内核模块
  • kernel_modules: 具体的编译规则
  • clean: 清理编译结果

五、驱动编译与测试流程

1. 驱动编译

make -C /path/to/kernel/source M=$(pwd) modules

2. 加载驱动

insmod newchrled.ko

3. 创建设备文件

mknod /dev/newchrled c 200 0

4. 运行应用程序

./newchrledAPP /dev/newchrled 1  # 打开LED
./newchrledAPP /dev/newchrled 0  # 关闭LED

六、硬件配置说明

1. GPIO配置

  • 使用GPIO1_IO03引脚控制LED
  • 配置时钟:CCM_CGR1寄存器第26位
  • 复用选择:SW_MUX_GPIO1_IO03寄存器设置为0x5
  • PAD配置:SW_PAD_GPIO1_IO03寄存器设置为0x10B0
  • 方向设置:GPIO1_GDIR寄存器第3位设为1(输出)
  • 数据设置:通过GPIO1_DR寄存器读写第3位

七、字符设备驱动开发要点

1. 设备号管理

  • 使用dev_t类型表示设备号
  • 使用MKDEV宏创建设备号
  • 使用MAJORMINOR宏提取主次设备号
  • 使用register_chrdev_regionalloc_chrdev_region注册设备号

2. cdev操作

  • 使用cdev_init初始化字符设备
  • 使用cdev_add将设备添加到内核

3. 自动创建设备节点

  • 使用class_create创建设备类
  • 使用device_create创建设备节点

4. 寄存器访问

  • 使用ioremap将物理地址映射到内核虚拟地址
  • 使用readlwritel进行寄存器读写

5. 文件操作

  • 实现file_operations结构体中的操作函数
  • openrelease用于设备打开和关闭
  • readwrite用于数据读写

八、用户空间与内核空间通信

1. copy_from_user

用于将用户空间数据复制到内核空间

unsigned long copy_from_user(void *to, const void __user *from, unsigned long n);

2. copy_to_user

用于将内核空间数据复制到用户空间

unsigned long copy_to_user(void __user *to, const void *from, unsigned long n);

九、模块参数与信息

1. MODULE_LICENSE

指定模块的许可证,这里是GPL

2. MODULE_AUTHOR

指定模块作者信息

十、错误处理

1. 返回值检查

  • 检查open函数返回值
  • 检查write函数返回值
  • 检查device_create等内核API返回值

2. 错误清理

  • 出现错误时需要释放已申请的资源
  • 包括取消地址映射、删除cdev、销毁设备和类等

十一、总结

本驱动程序完整实现了字符设备驱动的开发流程,包括:

  1. 寄存器配置与硬件操作
  2. 字符设备注册与管理
  3. 用户空间与内核空间通信
  4. 设备文件创建与访问
  5. 模块加载与卸载
  6. 错误处理与资源释放

Gitee 源码仓库

http://www.dtcms.com/a/356659.html

相关文章:

  • Go Vendor 和 Go Modules:管理和扩展依赖的最佳实践
  • Redis vs Elasticsearch:核心区别深度解析
  • Vue 项目首屏加载速度优化
  • Mysql系列--11、使用c/c++访问mysql服务
  • ViennaCL并行异构库介绍和使用
  • Pinterest自动化 “Pin“得高效
  • SpringMvc下
  • Oracle SQL 性能调优的基石:深入解读与驾驭执行计划
  • 商家协同生态下的复购革命:跨店收益如何激活12%增量消费
  • 【新启航】3D 逆向抄数的工具技术与核心能力:基于点云处理的扫描设备操作及模型重建方法论
  • 【活动回顾】“智驱未来,智领安全” AI+汽车质量与安全论坛
  • OpenEuler部署LoganaLyzer
  • 【开题答辩全过程】以 基于SpringBootVue的智能敬老院管理系统为例,包含答辩的问题和答案
  • 黑芝麻智能与云深处科技达成战略合作,共推具身智能平台全球市场应用
  • 基于互补素数与最小素因子性质的哥德巴赫猜想证明-陈墨仙
  • ROS2学习打卡表
  • 外卖大战之后,再看美团的护城河
  • vue3获取子组件
  • PostgreSQL15——子查询
  • 采用机器学习的苗期棉株点云器官分割与表型信息提取
  • GPT-Realtime架构与Token成本控制深度解析
  • Linux/UNIX系统编程手册笔记:基本概念
  • Redis面试题--介绍下Redis几种集群模式
  • Shell 秘典(卷二)——号令延展秘术 与 流程掌控心法・if 天机判语篇精解
  • 力扣p1011在D天送达包裹的能力 详解
  • docker-相关笔记
  • 网站加载慢,linux服务器接口请求响应变慢,怎么排查,一般是什么原因
  • 【算法】78.子集--通俗讲解
  • 开源网盘聚合工具Alist:统一管理20+云存储的技术实践
  • vue常见的指令都有哪些,有什么作用