【Linux下LED基础设备驱动】

寄存器映射及内存访问

内存基础概念

用户空间和内核空间

IO空间:

CPU通过IO操作设备上的寄存器实现对设备的控制,一般厂商按照IO空间性质将IO划分为IO端口和IO内存

IO端口:

采用独立编址方式。外部设备中的寄存器(IO端口)地址与内存地址是相互独立、互不影响的,它们分别位于不同的地址空间内

IO内存:

采用统一编址方式。外部设备中的寄存器和内存中的存储单元被同等看待,每个IO端口占用一个内存存储单元的地址,将内存地址的一部分划出来用作IO地址空间。外设寄存器占用了内存的地址空间,使内存的存储容量减小

申请/释放IO内存

地址映射

我们访问一个寄存器时,需要将物理地址转换成虚拟地址才能够访问,这个过程就是映射,映射分为动态映射和静态映射

**静态映射:**静态映射在内核启动过程中就建立好,整个内核的生命周期都存在

**动态映射:**动态映射在模块需要的时候才进行,使用完后要解除映射,只有映射时才占用地址资源

IO内存映射/释放映射

内存分配

申请/释放内存

gfp_mask

申请/释放内存(可申请任意大小)

申请/释放内存(申请更大内存)

IO内存读写

读IO内存

写IO内存

LED基础设备驱动

寄存器介绍

模式寄存器配置

输出类型寄存器

置位复位寄存器

LED灯驱动实现流程

1、搭建基础模块框架

2、模块加载函数

注册字符设备实现GPIOZ组基地址映射修改GPIOZ_MODER,配置PZ5、PZ6、PZ7模式为输出模式修改GPIOZ_OTYPER,配置PZ5、PZ6、PZ7为推挽输出修改GPIOZ_BSRR,配置PZ5、PZ6、PZ7输出低电平,关闭LED实现加载函数出错处理,出错则释放之前申请的资源,返回错误码

2、模块卸载函数

注销字符设备、取消地址映射

3、定义命令

创建头文件,将命令定义放在头文件中

4、实现struct file_operations结构体的unlocked_ioctl成员

获取用户的命令和参数将用户参数和命令组合成寄存器位偏移将0x1左移组合后的偏移量,设置复位置位寄存器,实现LED灯控制

led.h

cs 复制代码
#ifndef LED_HHHHH
#define LED_HHHHH

#define LED_TYPE 'H'

#define LED1_SET	_IOW(LED_TYPE, 0, int)
#define LED2_SET	_IOW(LED_TYPE, 1, int)
#define LED3_SET	_IOW(LED_TYPE, 2, int)

#define LED_ON		0x0
#define LED_OFF		0x1

#endif

led.c

cs 复制代码
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>

#include <asm/io.h>

#include "led.h"

MODULE_LICENSE("GPL");

static int led_major = 300;
static int led_minor = 0;

static int device_number = 1;

static struct cdev cdev;

#define GPIOZ_BASE_ADDR 0x54004000
#define GPIOZ_MODER		0x00
#define GPIOZ_OTYPER	0x04
#define GPIOZ_BSRR		0x18

void __iomem *gpioz_base;

static int led_open(struct inode *inodep, struct file *filp)
{
	printk("led open\n");
	return 0;
}

static int led_close(struct inode *inodep, struct file *filp)
{
	printk("led close\n");
	return 0;
}

static long led_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
	int state = 0;  
	int offset = 0;
	int tmp = 0;
	printk("led ioctl\n");

	/* 获取用户传递过来的参数存储在state变量中 */
	if(copy_from_user(&state, (void *)arg, sizeof(int)))
		return -EFAULT;

    /* 如果用户设置的LED灯状态为LED_OFF则操作BSRR寄存器的高16位 */
	if(state == LED_OFF) {
		offset += 16;
	}

	/* 判断命令执行对应操作 */
	switch(cmd) {
		case LED1_SET:
			/* LED1/PZ5位偏移 */
			offset += 5;
			break;
		case LED2_SET:
			/* LED2/PZ6位偏移 */
			offset += 6;
			break;
		case LED3_SET:
			/* LED3/PZ7位偏移 */
			offset += 7;
			break;
		default:
			return -EINVAL;
	}

	/* 设置指定GPIO输出状态 */
	tmp = 0x1 << offset; 				
	writel(tmp, gpioz_base + GPIOZ_BSRR);

	return 0;
}

static struct file_operations led_fops = {
	.open = led_open,
	.release = led_close,
	.unlocked_ioctl = led_ioctl
};

int led_drv_init(void)
{
	int ret;
	unsigned int tmp;
	/* 构建设备号 */
	dev_t devno = MKDEV(led_major, led_minor);

	printk("led driver loading\n");
    /* 申请设备号 */
	ret = register_chrdev_region(devno, device_number, "led");
	if (ret < 0) {
		printk("failed: register_chrdev_region\n");
		return ret;
	}
    /* cdev初始化 */
	cdev_init(&cdev, &led_fops);

	/* 设备注册 */
	ret = cdev_add(&cdev, devno, device_number);
	if (ret < 0) {
		printk("faile: cdev_add\n");
		goto err1;
	}

	/* 寄存器映射 */
	gpioz_base = ioremap(GPIOZ_BASE_ADDR, 0xff);
	if (!gpioz_base) {
		printk("failed: ioremap\n");
		goto err2;
	}

	/* 设置GPIO口工作模式 */
	tmp = readl(gpioz_base + GPIOZ_MODER);
	tmp = (tmp & ~(0x3 << 10)) | 0x1 << 10; //设置PZ5为输出
	tmp = (tmp & ~(0x3 << 12)) | 0x1 << 12; //设置PZ6为输出
	tmp = (tmp & ~(0x3 << 14)) | 0x1 << 14; //设置PZ7为输出
	writel(tmp, gpioz_base + GPIOZ_MODER);

    /* 设置GPIO输出类型 */
	tmp = readl(gpioz_base + GPIOZ_OTYPER);
	tmp &= ~(0x7 << 5); 						//设置PZ5 PZ6 PZ7为推挽输出
	writel(tmp, gpioz_base + GPIOZ_OTYPER);

	/* 设置GPIO默认输出状态 */
	tmp = 0x7 << (5 + 16); 				//设置PZ5 PZ6 PZ7输出低电平,关闭LED灯
	writel(tmp, gpioz_base + GPIOZ_BSRR);

	return 0;

	/* 出错处理依次释放掉前文申请的资源 */
err2:
	cdev_del(&cdev);
err1:
	unregister_chrdev_region(devno, device_number);
	return ret;
}

void led_drv_exit(void)
{
	/* 构建设备号 */
	dev_t devno = MKDEV(led_major, led_minor);

	printk("led driver unloading\n");
	/* 去掉寄存器地址映射 */
	iounmap(gpioz_base);
	/* 设备注销 */
	cdev_del(&cdev);
	/* 释放设备号 */
	unregister_chrdev_region(devno, device_number);
}

module_init(led_drv_init);
module_exit(led_drv_exit);

MODULE_AUTHOR("led <caozm_yf@hqyj.com>");

LED灯应用层实现流程

1、应用程序

打开字符设备

设置向模块传递的参数,用于开关灯

通过ioctl循环向模块发送命令和参数,延时1s

test.c

cs 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <string.h>

#include "led.h"

int main(int argc, const char *argv[])
{
	int fd, state;

	fd = open("/dev/led", O_RDWR);
	if (fd < 0) {
		perror("open");
		return -1;
	}

	while(1) {
		state = LED_ON;
		ioctl(fd, LED1_SET, &state);
		ioctl(fd, LED2_SET, &state);
		ioctl(fd, LED3_SET, &state);
		sleep(1);
		state = LED_OFF;
		ioctl(fd, LED1_SET, &state);
		ioctl(fd, LED2_SET, &state);
		ioctl(fd, LED3_SET, &state);
		sleep(1);
	}

	close(fd);
	
	return 0;
}
相关推荐
子琦啊30 分钟前
【算法复习】字符串 | 两个底层直觉,吃透高频题
linux·运维·算法
AOwhisky1 小时前
Kubernetes 学习笔记:集群管理、命名空间与 Pod 基础
linux·运维·笔记·学习·云原生·kubernetes
小龙在慢慢变强..2 小时前
目录结构(FHS 标准)
linux·运维·服务器
2035去旅行2 小时前
嵌入式开发,如何选择C标准库
linux·arm开发
刘延林.2 小时前
win11系统下通过 WSL2 安装Ubuntu 24.04 使用RTX 5080 GPU
linux·运维·ubuntu
CodeOfCC3 小时前
Linux 嵌入式arm64安装openclaw
linux·运维·服务器
宵时待雨4 小时前
linux笔记归纳3:linux开发工具
linux·运维·笔记
magrich5 小时前
安装NoMachine并解决无外接显示器桌面黑屏
linux·运维·服务器
进击的小头5 小时前
20_第20篇:嵌入式外设驱动开发基础:寄存器级开发与库函数开发对比实战
arm开发·驱动开发·单片机
低调小一5 小时前
BDD(行为驱动开发)入门:把“测试”写成“行为”,把“需求”写成“场景”
驱动开发·tdd·bdd