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

news/2024/11/26 6:25:18/

一,简述

(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);//判断应用层传来的数据是不是大于咱们自己声明的数组长度,如果大于给转成128ret=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);//判断应用层传来的数据是不是大于咱们自己声明的数组长度,如果大于给转成128ret=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);//物理地址转换虚拟地址 红灯GPIOa28if(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;
}


http://www.ppmy.cn/news/974783.html

相关文章

C++ GDAL找出多时相遥感影像缺失的日期并自动生成新的全零图像作为替补

本文介绍基于C 语言的GDAL库&#xff0c;基于一个存储大量遥感影像的文件夹&#xff0c;依据每一景遥感影像的文件名中表示日期的那个字段&#xff0c;找出这些遥感影像中缺失的成像日期&#xff0c;并新生成多个像元值全部为0的栅格文件&#xff0c;作为这些缺失日期当日的遥感…

59. 螺旋矩阵 II

题目链接&#xff1a;力扣 解题思路&#xff1a;类似于54. 螺旋矩阵 找规律&#xff0c;模拟矩阵的访问&#xff0c;每访问一个位置&#xff0c;就对该位置进行赋值&#xff0c; 观察螺旋顺序&#xff1a;行(向右)--> 列(向下)-->行(向左)-->列(向上)-->行(向右…

12.面板问题

面板问题 html部分 <h1>Lorem ipsum dolor sit, amet consectetur adipisicing.</h1><div class"container"><div class"faq"><div class"title-box"><h3 class"title">Lorem, ipsum dolor.<…

linux:secureCRT通过pem证书远程访问服务器

参考&#xff1a; secureCRT通过pem证书远程访问服务器_Fengshana的博客-CSDN博客 总结&#xff1a; 配置公钥即可

“深入解析Spring Boot:从入门到精通“

标题&#xff1a;深入解析Spring Boot&#xff1a;从入门到精通 摘要&#xff1a;本文将全面介绍Spring Boot框架的基本概念、核心特性、使用方法以及提供示例代码&#xff0c;帮助读者从入门到精通Spring Boot开发。 引言 Spring Boot是一个用于快速构建基于Spring框架的应用…

Android:Service服务简单应用

public class MyService extends Service { public MyService() { System.out.println("MyServiceMyService()"); } Override public void onCreate() { super.onCreate(); System.out.println("MyService创建onCreate()&quo…

2023/7/23周报

目录 摘要 论文阅读 1、题目和现存问题 2、问题阐述及相关定义 3、LGDL模型框架 4、实验准备 5、实验过程 深度学习 1、GCN简单分类任务 2、文献引用数据分类案例 3、将时序型数据构建为图数据格式 总结 摘要 本周在论文阅读上&#xff0c;对基于图神经网络与深度…

C语言编程---案例练习

文章目录 格式化输出 格式化输出 %d&#xff0c;输出整数&#xff1b; %f&#xff0c;输出浮点数&#xff1b;%.3f 保留三位小数&#xff1b; %e&#xff0c;输出双精度浮点数&#xff1b; %c&#xff0c;输出单个字符&#xff1b;将字符格式化%d&#xff0c;即转ASCII码&…