通过字符设备驱动点亮板子上的led灯

通过字符设备驱动点亮板子上的led灯

app:      test.c  char buf[3]

                       1 0 0

   0 1 0

   0 0 1

------------------|------------------------

kernel:   led_driver.c

-------------------|------------------------

hardware:  RGB_led

  • 应用程序如何将数据传递给驱动读写的方向是站在用户的角度来说的

函数

1)从用户空间拷贝数据到内核空间(用户需要写数据的时候)

#include <linux/uaccess.h>
int copy_from_user(void *to, const void __user *from, int n)
	功能:从用户空间拷贝数据到内核空间(用户需要写数据的时候)
	参数:
		@to  :内核中内存的首地址
		@from:用户空间的首地址
		@n   :拷贝数据的长度(字节)
	返回值:成功返回0,失败返回未拷贝的字节的个数

2)从内核空间拷贝数据到用户空间(用户开始读数据

int copy_to_user(void __user *to, const void *from, int n)
	功能:从内核空间拷贝数据到用户空间(用户开始读数据)
	参数:
		@to  :用户空间内存的首地址
		@from:内核空间的首地址
		@n   :拷贝数据的长度(字节)
	返回值:成功返回0,失败返回未拷贝的字节的个数

驱动如何操作寄存器

rgb_led灯的寄存器是物理地址,在linux内核启动之后,在使用地址的时候,操作的全是虚拟地址需要将物理地址转化为虚拟地址。在驱动代码中操作的虚拟地址就相当于操作实际的物理地址

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

3)将物理地址映射成虚拟地址

虚拟首地址 = ioremap(物理基地址,字节大小)
void * ioremap(phys_addr_t offset, unsigned long size)
(当__iomen告诉编译器,取的时候是一个字节大小)
功能:将物理地址映射成虚拟地址
参数:
	@offset :要映射的物理的首地址
	@size   :大小(字节)(映射是以业为单位,一页为4K,就是当你小于4k的时候映射的区域都为4k)
返回值:成功返回虚拟地址,失败返回NULL((void *)0); 

4)取消映射

void iounmap(void  *addr)
		功能:取消映射
		参数:	
			@addr :虚拟地址
		返回值:无
#define	ENOMEM		12	/* Out of memory */

操作步骤

1、//通过虚拟地址对寄存器更改设置值,实行对硬件的初始化

用户使用发送对硬件控制的指令给底层驱动,底层驱动驱动硬件做对应动作:

 应用层write,驱动对应write获取到传送的指令:

copy_from_user(内核空间首地址,用户空间地址,拷贝字节数);

2 //将用户空间数据拷贝到内核空间

应用层read,驱动对应read需要将内核空间数据拷贝用户空间:

copy_to_user(用户空间地址,内核空间首地址,拷贝字节数);、

Eg:点灯

  • 软件编程控制硬件的思想:

只需要向控制寄存器中写值或者读值,就可以让我们处理器完成一定的功能。

RGB_led 

1》GPIOxOUT:控制引脚输出高低电平

RED_LED--->GPIOA28

GPIOAOUT --->  0xC001A000

GPIOA28输出高电平:

GPIOAOUT[28]  <--写--  1

GPIOA28输出低电平:

GPIOAOUT[28]  <--写--  0

2》GPIOxOUTENB:控制引脚的输入输出模式

GPIOAOUTENB  ---> 0xC001A004

设置GPIOA28引脚为输出模式:

GPIOAOUTENB[28] <--写-- 1

3》GPIOxALTFN:控制引脚功能的选择

GPIOAALTFN1  ---> 0xC001A024

设置GPIOA28引脚为GPIO功能:

GPIOAALTFN1[25:24]  <--写-- 0b00

00 = ALT Function0   

01 = ALT Function1 

10 = ALT Function2   

11 = ALT Function3 

GPIO引脚功能的选择:每两位控制一个GPIO引脚

red  :gpioa28

GPIOXOUT   :控制高低电平的   0xC001A000

GPIOxOUTENB:输入输出模式    0xC001A004

GPIOxALTFN1:function寄存器  0xC001A024

一个寄存器36个字节

green:gpioe13

0xC001e000

blue :gpiob12

0xC001b000

练习:

1.字符设备驱动实现流水灯(30分钟)

//读,改,写

writel(v,c)

功能:向地址中写一个值

参数:

@ v :写的值

@ c :地址

readl(c)

功能:读一个地址,将地址中的值给返回

参数:

@c :地址

七色LED流水灯驱动

内核层 led驱动函数

#include <linux/module.h>
#include <linux/init.h>
#include <linux/printk.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <asm/io.h>
#define NAME "chrdev_devled"
//定义宏保存物理地址基地址
#define RED_BASE 0xc001a000
#define GRE_BASE 0xc001e000
#define BLU_BASE 0xc001b000
//定义指针保存映像后的虚拟地址首地址
unsigned int *red_addr = NULL;
//定义指针保存映像后的虚拟地址首地址
unsigned int *gre_addr = NULL;
//定义指针保存映像后的虚拟地址首地址
unsigned int *blu_addr = NULL;
int size=32;
int major = 0; //保存主设备号
char kbuf[32];//
char kbuf_r[32]="welcome to hqyj";
// open read write release 初始化
int myopen(struct inode *node, struct file *file_t)
{
    printk("%s %s %d\n", __FILE__, __func__, __LINE__);
    return 0;
}
ssize_t myread(struct file *file_t, char __user *ubuf, size_t n, loff_t *off_t)
{
    printk("%s %s %d\n", __FILE__, __func__, __LINE__);
    //将内核空间数据拷贝到用户空间
    if(sizeof(kbuf_r)<size)
    size=sizeof(kbuf_r);
    if(copy_to_user(ubuf,kbuf_r,size)!=0)
    {
        printk("copy_to_user err.");
        return -EINVAL;
    }
    // printk("kbuf=%s\n",kbuf);
    return 0;
}
ssize_t mywrite(struct file *file_t, const char __user *ubuf, size_t n, loff_t *off_t)
{
    printk("%s %s %d\n", __FILE__, __func__, __LINE__);
    //将用户空间—(ubuf) 的数据拷贝到内核空间(kbuf)
    if(sizeof(kbuf)<size)
    size=sizeof(kbuf);
    if(copy_from_user(kbuf,ubuf,size)!=0)
    {
        printk("copy_from_user err.");
        return -EINVAL;
    }
    printk("kbuf=%s\n",kbuf);
    if(kbuf[0]==1)
    {
        //红灯开
        *red_addr |= (1<<28);
    }
    else if(kbuf[0]==0)
    {
        //红灯关
         *red_addr &= (~(1<<28));
    }
    if(kbuf[1]==1)
    {
        //红灯开
        *gre_addr |= (1<<13);
    }
    else if(kbuf[1]==0)
    {
        //红灯关
         *gre_addr &= (~(1<<13));
    }
    if(kbuf[2]==1)
    {
        //蓝灯开
        *blu_addr |= (1<<12);
    }
    else if(kbuf[2]==0)
    {
        //蓝灯关
         *blu_addr &= (~(1<<12));
    }
    return 0;
}
int myclose(struct inode *node, struct file *file_t)
{
    printk("%s %s %d\n", __FILE__, __func__, __LINE__);
    return 0;
}
struct file_operations fops = {
    .open = myopen,
    .read = myread,
    .write = mywrite,
    .release = myclose,
};
//入口函数
static int __init chrdev_init(void)
{
    printk("%s %s %d\n", __FILE__, __func__, __LINE__);
    //注册字符设备驱动: 主设备号 驱动名 结构体
    major=register_chrdev(major, NAME, &fops);
    //容错判断
    if(major < 0)
    {
        printk("chrdev_register err.\n");
        return -EINVAL;
    }
    //建立虚拟地址和物理地址之间的映射关系——控制红灯
    red_addr = (unsigned int *)ioremap(RED_BASE,40);
    if(red_addr == NULL)
    {
        printk("ioremap red err.\n");
        return -EINVAL;
    }
    //初始化红灯
    *(red_addr+9) &=(~(3<<24));//选择GPIOA28功能
     *(red_addr+1) |=(1<<28);//选择输出使能
      *red_addr &=(~(1<<28));//红灯关闭
      //建立虚拟地址和物理地址之间的映射关系——控制绿灯
    gre_addr = (unsigned int *)ioremap(GRE_BASE,40);
    if(red_addr == NULL)
    {
        printk("ioremap red err.\n");
        return -EINVAL;
    }
       //初始化绿灯
    *(gre_addr+8) &=(~(3<<26));//选择GPIOE13功能
    *(gre_addr+1) |=(1<<13);//选择输出使能
    *gre_addr &=(~(1<<13));//绿灯关闭
    //建立虚拟地址和物理地址之间的映射关系——控制蓝灯
    blu_addr = (unsigned int *)ioremap(BLU_BASE,40);
    if(blu_addr == NULL)
    {
        printk("ioremap red err.\n");
        return -EINVAL;
    }
       //初始化蓝灯
    *(blu_addr+8) &=(~(0<<24));//选择GPIOB12功能
    *(blu_addr+8) |=(1<<25);//选择GPIOB12功能
    *(blu_addr+1) |=(1<<12);//选择输出使能
    *blu_addr &=(~(1<<12));//蓝灯关闭
    return 0;
}
//出口函数
static void __exit chrdev_exit(void)
{
    printk(KERN_ERR "%s %s %d\n", __FILE__, __func__, __LINE__);
    //取消映射
    iounmap(red_addr);
    //注销字符设备驱动
    unregister_chrdev(major, NAME);
}
module_init(chrdev_init);//入口
module_exit(chrdev_exit);//出口
MODULE_LICENSE("GPL");//协议

Makefile

KERNEL_PATH=/home/hq/kernel/kernel-3.4.39  #开发板的路径
#KERNEL_PATH=/lib/modules/$(shell uname -r)/build #虚拟机路径

PWD=$(shell pwd)  #将shell命令pwd执行的结果赋值给PWD变量
all:
	make -C $(KERNEL_PATH) M=$(PWD) modules
	# -C 路径:指定到切换到那个路径,执行make modules命令
	#  M=路径:指定需要编译的驱动代码所在的路径

.PHONY:clean
clean:
	make -C $(KERNEL_PATH) M=$(PWD) clean

obj-m += chrdev.o

#要编译生成驱动模块的目标程序

应用层

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

void delay()
{
    int i;
    while(i<100)
    {
        i++;
    }
}
int main(int argc,char *argv[])
{
    int fd = open(argv[1],O_RDWR);
    if(fd < 0)
    {
        printf("open %s failed\n",argv[1]);
        return 2;
    }
    char buf[32] = {1,0,0};
    int i=0;
    int j=0;
    int z=0;
    while(1)
    {
        write(fd,buf,sizeof(buf));
        // for(;i<3;i++)
        // {
            
        //     buf[0]=buf[0]?0:1;
        //     delay();
        //     for(;j<2;j++)
        //     {
                
        //         buf[1]=buf[1]?0:1;
        //         delay();
        //         for(;z<1;z++)
        //         {
                    
        //             buf[2]=buf[2]?0:1;
        //             delay();
        //         }
        //     }
        // }

        sleep(1);
        buf[0]=1;
        buf[1]=0;
        buf[2]=0;
        write(fd,buf,sizeof(buf));
        sleep(1);
        buf[0]=0;
        buf[1]=1;
        buf[2]=0;
        write(fd,buf,sizeof(buf));
        sleep(1);
        buf[0]=0;
        buf[1]=0;
        buf[2]=1;
        write(fd,buf,sizeof(buf));
        sleep(1);
        buf[0]=1;
        buf[1]=1;
        buf[2]=0;
        write(fd,buf,sizeof(buf));
        sleep(1);
        buf[0]=1;
        buf[1]=0;
        buf[2]=1;
        write(fd,buf,sizeof(buf));
        sleep(1);
        buf[0]=0;
        buf[1]=1;
        buf[2]=1;
        write(fd,buf,sizeof(buf));
        sleep(1);
        buf[0]=1;
        buf[1]=1;
        buf[2]=1;
        write(fd,buf,sizeof(buf));

        // sleep(1);
        // // buf[2]=buf[2]?0:1;
        // buf[0]=buf[0]?0:1;
        // // buf[1]=buf[1]?0:1;
        // write(fd,buf,sizeof(buf));

        // sleep(1);
        // // buf[0]=buf[0]?0:1;
        // buf[1]=buf[1]?0:1;
        // // buf[2]=buf[2]?0:1;
        //  write(fd,buf,sizeof(buf));
     
        // sleep(1);
        // // buf[1]=buf[1]?0:1;
        // buf[2]=buf[2]?0:1;
        // write(fd,buf,sizeof(buf));

    }
    
    close(fd);
    return 0;
}

设备节点创建问题(udev/mdev)

(mknod hello  c 243 0,手动创建设备节点hello)

宏有返回值:为最后一句话执行的结果)

mknod 设备节点名 c/b 主设备号 次设备号

1、设置自动创建设备节点:

struct class *cls=class_create(THIS_MODULE,名字); 

struct device *dev=device_create(cls,NULL,\

MKDEV(主设备号,次设备号),NULL,设备节点的名字);

2、卸载驱动:设置自动销毁设备节点

device_destroy(cls,MKDEV(major,0));

class_destroy(cls);

1)自动创建设备节点:

struct class *cls;
#include <linux/device.h>
cls = class_create(owner, name)	
void class_destroy(struct class *cls)//销毁
	功能:向用户空间提交目录信息(内核目录的创建)
	参数:
		@owner :THIS_MODULE(看到owner就添THIS_MODULE)
		@name  :目录名字
	返回值:成功返回struct class *指针
			失败返回错误码指针 int (-5)	
if(IS_ERR(cls))
{	
	return PTR_ERR(cls);(PTR_ERR:把错误码指针转换成错误码)	
}	

IS_ERR(): 返回值为0,不在错误码地址范围,非0,在错误码地址范围,内核从0xffffffff 地址开始往地址减少的方向,预留了4K空间用来作为错误码的地址。

2)向用户空间提交文件信息

struct device *device_create(struct class *class, struct device *parent,dev_t devt, void *drvdata, const char *fmt, ...)
//(内核文件的创建),每个文件对应一个外设(硬件设备)
/void device_destroy(struct class *class, dev_t devt)//销毁
	功能:向用户空间提交文件信息
	参数:
		@class :目录名字
		@parent:NULL
		@devt  :设备号 (major<<12 |0  < = > MKDEV(major,0)
		@drvdata :NULL
		@fmt   :文件的名字
	返回值:成功返回struct device *指针
			失败返回错误码指针 int (-5)

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

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

相关文章

MySQL定时备份实现

一、备份数据库 –all-databases 备份所有数据库 /opt/mysqlcopy/all_$(date “%Y-%m-%d %H:%M:%S”).sql 备份地址 docker exec -it 容器名称 sh -c "mysqldump -u root -ppassword --all-databases > /opt/mysqlcopy/all_$(date "%Y-%m-%d %H:%M:%S").sq…

Docker 安装 MySQL5.7 和 MySQL8

文章目录 安装 MySQL5.7拉取镜像前期准备&#xff1a;启动容器 安装MySQL8.0拉取镜像查看镜像前期准备启动容器 安装 MySQL5.7 拉取镜像 docker pull mysql:5.7拉下来镜像后 执行 docker images 此时我们已经有这个镜像了。 前期准备&#xff1a; 在根目录下创建 app &…

Redis案例实战之Bitmap、Hyperloglog、GEO

&#x1f44f;作者简介&#xff1a;大家好&#xff0c;我是爱吃芝士的土豆倪&#xff0c;24届校招生Java选手&#xff0c;很高兴认识大家&#x1f4d5;系列专栏&#xff1a;Spring源码、JUC源码、Kafka原理、分布式技术原理、数据库技术&#x1f525;如果感觉博主的文章还不错的…

Goland配置leetcode

1. 安装 首先在goland的setting界面上找到Plugins&#xff0c;然后搜索关键字leetcode&#xff0c;找到LeetCode Editor&#xff0c;安装它。 在安装后&#xff0c;第一次需要对其进行配置&#xff0c;在Tools中找到LeetCode Plugins&#xff0c;如下图所示进行配置。首先国内…

宝塔面板Linux服务器CentOS 7数据库mysql5.6升级至5.7版本教程

近段时间很多会员问系统更新较慢&#xff0c;也打算上几个好的系统&#xff0c;但几个系统系统只支持MYSQL5.7版本&#xff0c;服务器一直使用较低的MYSQL5.6版本&#xff0c;为了测试几个最新的系统打算让5.6和5.7并存使用&#xff0c;参考了多个文档感觉这种并存问题会很多。…

PHP+MySQL组合开发:万能在线预约小程序源码系统 附带完整的搭建教程

近年来&#xff0c;线上服务逐渐成为市场主流。特别是在预约服务领域&#xff0c;用户越来越倾向于选择方便快捷的线上预约方式。传统的预约方式如电话预约和到店预约不仅效率低下&#xff0c;而且在信息传达上存在很大的误差。这使得用户常常需要反复确认&#xff0c;浪费了大…

.NET 8最强新功能:键控服务依赖注入

什么是键控服务依赖注入&#xff1f; 在之前的依赖注入中&#xff0c;服务是根据其类型进行注册和解析的。如果出现同一接口有多个实现怎么办呢&#xff1f;这时候就可以使用.NET 8的新功能“键控服务依赖注入”。它允许您注册接口的多个实现&#xff0c;每个实现都与一个唯一…

10.Go 映射

映射&#xff08;map&#xff09;是一种特殊的数据结构&#xff0c;用于存储一系列无序的键值对&#xff0c;映射基于键来存储数据。映射功能强大的地方是&#xff0c;能够基于键快速检索数据。键就像索引一样&#xff0c;指向与该键关联的值。与C、Java中的映射的不同之处在于…

ECMAScript 的未来:预测 JavaScript 创新的下一个浪潮

以下是简单概括关于JavaScript知识点以及一些目前比较流行的比如&#xff1a;es6 想要系统学习&#xff1a; 大家有关于JavaScript知识点不知道可以去 &#x1f389;博客主页&#xff1a;阿猫的故乡 &#x1f389;系列专栏&#xff1a;JavaScript专题栏 &#x1f389;ajax专栏&…

Cross-Drone Transformer Network for Robust Single Object Tracking论文阅读笔记

Cross-Drone Transformer Network for Robust Single Object Tracking论文阅读笔记 Abstract 无人机在各种应用中得到了广泛使用&#xff0c;例如航拍和军事安全&#xff0c;这得益于它们与固定摄像机相比的高机动性和广阔视野。多无人机追踪系统可以通过从不同视角收集互补的…

一站式指南:第 377 场力扣周赛的终极题解

比赛详情 比赛地址 题目一很简单题目二主要是题目长了点&#xff0c;其实解法很常规(比赛后才意识到)题目三套用Dijkstra算法题目四没时间解答水平还有待提升(其实就是需要灵活组合运用已知的算法&#xff0c;有点类似大模型的Agent) 题解和思路 第一题&#xff1a;最小数字…

AI时代下,如何看待“算法利维坦”?程序员客栈程序员客栈​

ChatGPT的浪潮从2022年袭来后&#xff0c;至今热度不减&#xff0c;呈现出蓬勃发展的趋势。AI家居、医疗、教育、金融、公益、农业、艺术......AI真的已经走进了生活的方方面面&#xff0c;我们仿佛已经进入了AI时代&#xff0c;势不可挡。人工智能水平如此之高&#xff0c;不禁…

云HIS源码 云HIS解决方案 支持医保功能

云HIS系统重建统一的信息架构体系&#xff0c;重构管理服务流程&#xff0c;重造病人服务环境&#xff0c;向不同类型的医疗机构提供SaaS化HIS服务解决方案。 云HIS作为基于云计算的B/S构架的HIS系统&#xff0c;为基层医疗机构&#xff08;包括诊所、社区卫生服务中心、乡镇卫…

RPA数据统计与展示

随着企业RPA机器人部署规模越来越庞大&#xff0c;更需要完善精细的管理与规划。这些进行自动化工作的数字员工&#xff0c;就像是传统的真实员工一样&#xff0c;也需要对日常的工作做好管理&#xff0c;对未来的发展做好规划&#xff0c;要实现这点&#xff0c;首先需要对RPA…

图像质量评估方法——结构相似性指数(SSIM)

结构相似性指数&#xff08;SSIM&#xff09;是一种全参考图像质量评估方法&#xff0c;用于比较两幅图像的相似性。 SSIM的计算涉及到亮度&#xff08;Luminance&#xff09;、对比度&#xff08;Contrast&#xff09;和结构&#xff08;Structure&#xff09;三个方面的相似性…

NET中使用Identity+CodeFirst+Jwt实现登录、鉴权

目录 前言 一、创建上下文类 1.自定义MyContext上下文类继承IdentityDbContext 2.在Program中添加AddDbContext服务 二、使用Migration数据迁移 1.在控制台中 依次使用add-migration 、updatebase 命令 2.如何修改表名 3.如何自定义字段 三、使用Identity实现登录、修改密码 …

【网络奇遇记】揭秘计算机网络的性能指标:速率|带宽|吞吐量|时延

&#x1f308;个人主页&#xff1a;聆风吟 &#x1f525;系列专栏&#xff1a;网络奇遇记、数据结构 &#x1f516;少年有梦不应止于心动&#xff0c;更要付诸行动。 文章目录 &#x1f4cb;前言一. 速率1.1 数据量1.2 速率 二. 带宽三. 吞吐量四. 时延4.1 发送时延4.2 传播时延…

编解码异常分析

前言 最近在做的项目&#xff0c;有H264解码的需求。部分H264文件解码播放后&#xff0c;显示为绿屏或者花屏。 分析 如何确认是否是高通硬解码的问题 adb 指令 adb root adb remount adb shell setenforce 0 adb shell setprop vendor.gralloc.disable_ubwc 1 adb shell c…

基于JAVA的校园电商物流云平台 开源项目

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 数据中心模块2.2 商品数据模块2.3 快递公司模块2.4 物流订单模块 三、系统设计3.1 用例设计3.2 数据库设计3.2.1 商品表3.2.2 快递公司表3.2.3 物流订单表 四、系统展示五、核心代码5.1 查询商品5.2 查询快递公司5.3 查…

模型稳定性评估 简介

OOT OOT&#xff08;Out of Time&#xff09;是在建模过程中用于描述模型在时间维度上的性能变化的术语。当模型在训练时使用的数据集与实际应用场景中的数据分布发生差异&#xff0c;特别是在时间上存在间隔时&#xff0c;就可能出现OOT问题。 OOT通常指的是模型在训练时使用…