【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;
}
相关推荐
cyber_两只龙宝2 小时前
haproxy--使用socat工具实现对haproxy权重配置的热更新
linux·运维·负载均衡·haproxy·socat
٩( 'ω' )و2602 小时前
linux网络--基础概念
linux·网络
zhang6183992 小时前
Linux中不同服务器之间迁移python 虚拟环境-conda-pack
linux·运维·python
HIT_Weston2 小时前
121、【Ubuntu】【Hugo】首页板块配置:list 模板(一)
linux·ubuntu·list
The森2 小时前
万字长文外加示例:进入内核理解Linux 文件描述符(fd) 和 “一切皆文件” 理念
linux·经验分享·笔记
历程里程碑2 小时前
Linux19 实现shell基本功能
linux·运维·服务器·算法·elasticsearch·搜索引擎·哈希算法
wdfk_prog2 小时前
[Linux]学习笔记系列 --[drivers]mmc]mmc
linux·笔记·学习
嵌入小生0072 小时前
数据结构 | 常用排序算法大全及二分查找
linux·数据结构·算法·vim·排序算法·嵌入式
EverydayJoy^v^2 小时前
RH134学习进程——十二.运行容器(3)
linux·容器