项目名称:驱动开发(控LED灯,控制蜂鸣器)

文章详细介绍了Linux系统的组成部分,包括内核的五种管理功能,以及驱动程序的移植步骤。重点讲解了字符设备驱动的注册和注销,以及如何通过内核API实现数据的用户空间与内核空间交互。此外,还展示了如何控制硬件设备如LED灯,并提供了驱动代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一,简述

(1)Linux系统组成

()app:                                                [0-3G]

---------------------------------系统调用(软中断)---------------------

kernel:                                         【3-4G】

5种功能:

进程管理:进程的创建、销毁、调度等功能

文件管理:通过文件系统ext2/ext3/ext4  yaff  jiffs等来组织管理文件

网络管理 :通过网络协议栈对数据进程封装和拆解的过程

内存管理 :通过内存管理器对用户空间和内核空间内存的申请和释放

设备管理:设备驱动的管理

字符设备驱动:

  1. 按照字节为单位进行访问,顺序访问
  2. 会创建设备文件,open read write close来访问

       块设备驱动:

  1. 按照块(512字节)(扇区)来访问,可以顺序访问,可以无序访问
  2. 会创建设备文件,open read write close来访问

       网卡设备驱动:按照网络数据包来收发的

(2)驱动移植

  1. 需要有一个驱动对应的 .c代码
  2. 把.c文件放入到对应的文件夹内(char)
  3. 修改Makefile-》添加上自己代码编译生成的.o文件-》保存退出
  4. 修改Kconfig生成自己的菜单  (3和4根据其他的仿写)-》保存退出
  5. 到顶层目录执行make menuconfig-》配置自己的驱动(M)
  6. 编译-》make modules
  7. 找到生成的.ko文件安装(insmod lcd.ko )

编译:

make uImage-->uImage(包含了新的驱动的内核)

make modules -->demo.ko

Makefile  modules:

编译模块的规则

    Y(要编译到内核中)  M(编译生成模块)  N(不编译驱动)

sudo insmod demo.ko  安装驱动

sudo rmmod  demo     卸载驱动

静态编译:编译之后生成的可执行程序可以单独执行

动态编译:编译之后生成的可执行程序需要依赖其他内核才能执行

内部编译:在内核源码树中编译

外部编译:在内核源码树外编译

(3)驱动模块

入口(安装):资源的申请

出口(卸载):资源的释放

许可证:GPL

#include <linux/init.h>
#include <linux/module.h>                                                                          
static int __init  hello_init(void) 
//__init将hello_in it放到.init.text段中
{
	return 0;
} 
static void __exit hello_exit(void)
		//__exit将hello_exit放到.exit.text段中
{
}
module_init(hello_init);
//告诉内核驱动的入口地址
module_exit(hello_exit);
//告诉内核驱动的出口地址
MODULE_LICENSE("GPL");
//许可证

(4)字符设备驱动

int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops)

功能:注册一个字符设备驱动

参数:

@major:主设备号  

 :如果你填写的值大于0,它认为这个就是主设备号

 :如果你填写的值为0,操作系统给你分配一个主设备号   

@name :名字    cat /proc/devices 查看设备名和主设备号

@fops :操作方法结构体

返回值:major>0 ,成功返回0,失败返回错误码(负数) (vi -t EIO 可以查看错误码)

major=0,成功主设备号,失败返回错误码(负数)      

void unregister_chrdev(unsigned int major, const char *name)

功能:注销一个字符设备驱动

参数:

major:主设备号

name:名字

返回值:无

二,相关知识点

应用程序如何将数据传递给驱动(读写方向站在用户角度)

int copy_from_user(void *to, const void __user *from, int n)

功能:从用户空间拷贝数据到内核空间

参数:

  to:内核中内存的首地址  from:用户空间的首地址  n:拷贝数据的长度

返回值:成功返回0,失败返回未拷贝的字节个数

int copy_to_user(void __user *to,const void *from,int n)

功能:从内核空间拷贝数据到用户空间

参数:

to:用户空间内存的首地址 from:内核空间的首地址 n:拷贝数据的长度

返回值:成功返回0,失败返回未拷贝的字节个数

控制LED灯:

驱动如何操作寄存器

rgb_led灯的寄存器是物理地址,在linux内核启动之后,

在使用地址的时候,操作的全是虚拟地址。需要将物理地址

转化为虚拟地址。在驱动代码中操作的虚拟地址就相当于

操作实际的物理地址。

物理地址<------>虚拟地址

void * ioremap(phys_addr_t offset, unsigned long size)

功能:将物理地址映射成虚拟地址

参数:

@offset :要映射的物理地址

@size   :大小(字节)

返回值:成功返回虚拟地址,失败返回NULL; 

void iounmap(void  *addr)

功能:取消映射

参数:

@addr :虚拟地址

返回值:无

RGB_led 

red  :gpioa28

GPIOXOUT   :控制高低电平的   0xC001A000

GPIOxOUTENB:输入输出模式    0xC001A004

GPIOxALTFN1:function寄存器  0xC001A024

green:gpioe13

0xC001e000

blue :gpiob12

0xC001b000

指针类型加1是加的他的类型大小

驱动控制灯

 三,代码开发

驱动层

#include <linux/init.h>
#include <linux/module.h>
#include <linux/printk.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/string.h>
#include <linux/device.h>

#define RED_BASE 0xC001A000 //红灯基地址
#define BLUE_BASE 0xC001B000 //蓝灯基地址
#define GREEN_BASE 0xC001E000 //绿灯基地址
#define FMQ_BASE 0xC001C000 //蜂鸣器基地址

unsigned int *red_base = NULL;//申请的红灯虚拟地址
unsigned int *blue_base = NULL;//申请的蓝灯虚拟地址
unsigned int *green_base = NULL;//申请的绿灯虚拟地址
unsigned int *fmq_base = NULL;//申请的蜂鸣器虚拟地址

#define CNAME "hello"//设备名称
int major = 0;//设备号
int r_led=0,b_led=0,g_led=0;
char kbuf[128] = {0};//数据缓存区
struct class *cls;//自动创建设备节点 目录返回
struct device *dev;//自动创建设备节点 信息返回
int mycdev_open(struct inode *inode, struct file *file)//自己的open函数
{
	printk("open\n");//应用层使用open函数时会打印这句信息
	return 0;
}
ssize_t mycdev_read(struct file *file, char __user *ubuf,//自己写的read函数
	size_t size, loff_t *offs)
{
	memset(kbuf,0,sizeof(kbuf));
	int ret;
	printk("read is 111\n");//应用层使用read函数时会打印这句信息
//红灯
	if(r_led){
		strcat(kbuf,"red_led_open;");
	}else{
		strcat(kbuf,"red_led_close;");
	}

//蓝灯
	if(b_led){
		strcat(kbuf,"blue_led_open;");
	}else{
		strcat(kbuf,"blue_led_close;");
	}
//绿灯
	if(g_led){
		strcat(kbuf,"green_led_open;");
	}else{
		strcat(kbuf,"green_led_close;");
	}
	if(size > sizeof(kbuf))
		size = sizeof(kbuf);//判断应用层传来的数据是不是大于咱们自己声明的数组长度,如果大于给转成128

	ret=copy_to_user(ubuf,kbuf,size);//将驱动层kbuf内数据读到应用层
	if(ret){ //如果错误进入
		printk("copy  to user error\n");
		return -EINVAL;//返回错误提示
	}

	return size;
}
ssize_t mycdev_write(struct file *file, const char __user *ubuf,
	size_t size, loff_t *offs)
{
	int ret;
	printk("this is write\n");//应用层使用write函数时会打印这句信息
	if(size > sizeof(kbuf)) 
		size = sizeof(kbuf);//判断应用层传来的数据是不是大于咱们自己声明的数组长度,如果大于给转成128
	ret=copy_from_user(kbuf,ubuf,size);//将应用层ubuf内数据写到驱动层
	if(ret){
		printk("copyfrom user error\n");
		return -EINVAL;
	}
//红灯
	if(strncmp(kbuf,"11",2)==0){//灭灯
		
		*red_base &= ~(1<<28);
		r_led=0;
	}
	if(strncmp(kbuf,"22",2)==0)//亮灯
	{
		
		*red_base |= (1<<28);
		r_led=1;
	}
//蓝灯
	if(strncmp(kbuf,"33",2)==0){
		//灭灯
		*blue_base &= ~(1<<12);
		b_led=0;
	}
	if(strncmp(kbuf,"44",2)==0)
	{
		//亮灯
		*blue_base |= (1<<12);
		b_led=1;
	}
//绿灯
	if(strncmp(kbuf,"55",2)==0){
		//灭灯
		*green_base &= ~(1<<13);
		g_led=0;
	}
	if(strncmp(kbuf,"66",2)==0)
	{
		//亮灯
		*green_base |= (1<<13);
		g_led=1;
	}
//蜂鸣器
	if(strncmp(kbuf,"77",2)==0){
		//蜂鸣器关
		*fmq_base &= ~(1<<14);
	}
	if(strncmp(kbuf,"88",2)==0)
	{
		//蜂鸣器开
		*fmq_base |= (1<<14);

	}
	return size;
}
int mycdev_close(struct inode *inode, struct file *file)
{
	printk("close");
	//应用层使用close函数时会打印这句信息
	return 0;
}

const struct file_operations fops = {//这个结构体为咱们自己写的,在注册驱动时被调用,这是APP层能调用驱动层上面咱们自己的的程序的关键
	.open    = mycdev_open,//将自己写的open函数给到API内
	.read    = mycdev_read,//将自己写的read函数给到API内
	.write   = mycdev_write,//将自己写的write函数给到API内
	.release = mycdev_close,//将自己写的close函数给到API内
};

static int __init mycdev_init(void)
{
	//注册字符设备驱动
	major = register_chrdev(major,CNAME,&fops);
	if(major < 0){
		printk("register device error\n");//注册失败打印
		return major;
	}

	red_base = ioremap(RED_BASE,36);//物理地址转换虚拟地址 红灯GPIOa28
	if(red_base == NULL){//转换失败提示
		printk("red ioremap error\n");
		return -ENOMEM;
	}
	blue_base = ioremap(BLUE_BASE,36);//物理地址转换虚拟地址 
	if(blue_base == NULL){//转换失败提示
		printk("blue ioremap error\n");
		return -ENOMEM;
	}
	green_base = ioremap(GREEN_BASE,36);//物理地址转换虚拟地址
	if(green_base == NULL){//转换失败提示
		printk("green ioremap error\n");
		return -ENOMEM;
	}
	fmq_base = ioremap(FMQ_BASE,36);//物理地址转换虚拟地址
	if(fmq_base == NULL){//转换失败提示
		printk("fmq ioremap error\n");
		return -ENOMEM;
	}
//红灯部分配置
	*red_base &= ~(1<<28);//配置为灭灯
	*(red_base+1) |= (1<<28);//配置为输出模式
	*(red_base+9) &= ~(3<<24);//配置为GPIO功能,基地址+9,16进制36是24
//蓝灯部分配置
	*blue_base &= ~(1<<12);
	*(blue_base+1) |= (1<<12);
	*(blue_base+8) &= ~(3<<24);
	*(blue_base+8) |= (1<<25);
//绿灯部分配置
	*green_base &= ~(1<<13);
	*(green_base+1) |= (1<<13);
	*(green_base+8) &= ~(3<<26);
//蜂鸣器部分配置
	*fmq_base &= ~(1<<14);
	*(fmq_base+1) |= (1<<14);
	*(fmq_base+8) &= ~(3<<28);
	*(fmq_base+8) |= (1<<28);

	//自动创建设备节点
	cls = class_create(THIS_MODULE,CNAME);//自动创建设备节点目录
	if(IS_ERR(cls)){//IS_ERR可以将cls 错误码指针转换成错误码
		printk("class create error\n");
		return PTR_ERR(cls);//失败返回错误码
	}

	dev = device_create(cls,NULL,MKDEV(major,0),NULL,CNAME);//自动创建设备节点信息
	if(IS_ERR(dev)){
		printk("device create error\n");
		return PTR_ERR(dev);//失败返回错误码
	}
	
	return 0;
}

static void __exit mycdev_exit(void)//出口
{
	device_destroy(cls,MKDEV(major,0));//注释掉自动创建节点 信息
	class_destroy(cls);//注释掉自动创建节点 目录
	iounmap(green_base);//注销掉虚拟地址,注意后申请的先注销
	iounmap(blue_base);
	iounmap(red_base);
	
	//注销字符设备驱动
	unregister_chrdev(major,CNAME);
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");


应用层(控制LED灯,蜂鸣器)


#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

char buf[6] = {0};//数据缓存

int main(int argc, const char *argv[])
{
	int fd;
	
	fd = open("./hello",O_RDWR);//打开文件
	if(fd == -1){
		perror("open error");//打开错误提示
		return -1;
	}
	while(1){
		write(fd,buf,sizeof(buf));//将buf写入驱动中用于控制LED亮灭
		sleep(1);//延时
		
		//buf[0] = buf[0]?0:1;//如果buf[0]为0则赋值1,不为赋值0
//红灯
	if(strncmp(buf,"11",2)==0){//灭灯
		
		printf("红灯灭");
		
	}
	else if(strncmp(buf,"22",2)==0)//亮灯
	{
		
		printf("红灯亮");
	}
//蓝灯
	else if(strncmp(buf,"33",2)==0){
		//灭灯
		printf("蓝灯灭")
	}
	else if(strncmp(buf,"44",2)==0)
	{
		//亮灯
		printf("蓝灯亮");
	}
//绿灯
	else if(strncmp(buf,"55",2)==0){
		//灭灯
		printf("绿灯灭");
	}
	else if(strncmp(buf,"66",2)==0)
	{
		//亮灯
		printf("绿灯亮");
	}
//蜂鸣器
	else if(strncmp(buf,"77",2)==0){
		
		printf("蜂鸣器关");
		
	}
	else if(strncmp(buf,"88",2)==0)
	{
		printf("蜂鸣器开");

	}
	}

	close(fd);//关闭

	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值