07-S3C2440驱动学习(一)嵌入式linux字符设备驱动-LED字符设备驱动

发布者:qpb1234最新更新时间:2022-05-28 来源: eefocus关键字:S3C2440  linux  字符设备驱动 手机看文章 扫描二维码
随时随地手机看文章

一、嵌入式linux字符设备驱动框架

写应用程序的人 不应该去看电路图,但是如何操作硬件呢:调用驱动程序里的open,read,write等来实现。


C库里实现了 open 、read、write上层函数


调用open等:swi val—引发一个异常中断,进入内核异常处理函数。


系统调用接口:根据发生中断的原因,调用处理函数(sys_open,sys_read等sys_open等函数会执行与open相关各种初始化函数,通知调用自己写好的open函数,这里注意应用程序的open不仅仅是调用驱动中的open,其他函数类似,是调用sys_open,sys_open里包含了驱动中实现的open)。


sys_open:根据调用的不同设备,执行不同的函数。

总结:

APP应用程序 调用open read(c库中实现,swi),swi val—引发一个异常中断,进入内核异常处理函数,根据发生中断的原因,调用处理函数(sys_open,sys_read等)。sys_open:根据调用的不同设备的属性,执行不同的函数。


问题:


App应用程序调用open等,最终调用的是相应设备的open,是如何对应起来的呢?依赖于驱动程序框架。


1.简单的字符驱动程序框架


主要包括以下几个部分,这一节我们先看看这几部分都有什么。第三节有完整的驱动框架代码。


(1)写出led_open,led_read等


static int first_drv_open(struct inode *inode, struct file *file)

{

printk("first_drv_openn");

return 0;

}

 

static ssize_t first_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)

{

printk("first_drv_writen");

return 0;

}


(2)怎么告诉内核呢?定义一个file_operations结构体,填充它:


static struct file_operations first_drv_fops = {

    .owner  =   THIS_MODULE,    /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */

    .open   =   first_drv_open,     

.write = first_drv_write,    

};

(3)怎么用起来这个结构体,就是怎么告诉内核?

//注册驱动程序


int major;

static int first_drv_init(void)

{

major = register_chrdev(0, "first_drv", &first_drv_fops); // 注册, 告诉内核

printk("first_drv_initn");

return 0;

}

static void first_drv_exit(void)

{

unregister_chrdev(major, "first_drv"); // 卸载

}

参数:主设备号,名字,结构体


(4)谁来调用这个函数呢:驱动的入口函数,需要一个宏来修饰一下:


module_init(first_drv_init);

module_exit(first_drv_exit);//函数指针

二、函数对应过程分析:

查看下设备节点信息:

C表示字符设备    4 主设备号 63次设备号


-常规文件


D目录.


当APP使用open的时候 首先判断设备属性是字符设备,然后根据主设备号 去数组中找到对应的file_operation结构体。


用设备类型+设备号(字符设备+主设备号)就可以找到fileoperation结构

register_chrdev最简单的实现方式:以Major为索引,把file_operation写进去,实现注册。

VFS如何根据要打开的东西,找到驱动程序:


VFS:根据字符设备数组,根据设备号找到file_operation结构


file_operation里:


入口函数里调用 regist_chrdev根据主设备号把file_operation放到数组里。


三、简单的字符驱动程序框架如下:

(1)可挂载驱动程序框架代码


#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

 

static int first_drv_open(struct inode *inode, struct file *file)

{

printk("first_drv_openn");

return 0;

}

 

static ssize_t first_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)

{

printk("first_drv_writen");

return 0;

}

 

static struct file_operations first_drv_fops = {

    .owner  =   THIS_MODULE,    /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */

    .open   =   first_drv_open,     

.write = first_drv_write,    

};

 

 

int major;

static int first_drv_init(void)

{

major = register_chrdev(0, "first_drv", &first_drv_fops); // 注册, 告诉内核

printk("first_drv_initn");

return 0;

}

 

static void first_drv_exit(void)

{

unregister_chrdev(major, "first_drv"); // 卸载

}

 

module_init(first_drv_init);

module_exit(first_drv_exit);


(2)Makefile

KERN_DIR = /home/book/yangfei/linux-2.6.22.6

 

all:

make -C $(KERN_DIR) M=`pwd` modules 

 

clean:

make -C $(KERN_DIR) M=`pwd` modules clean

rm -rf modules.order

 

obj-m += first_drv.o

驱动编译依赖于内核文件,因此需要提前解压内核源码并编译。

(3)执行make,生成.ko文件 并拷贝至NFS文件夹


make


cp first_drv.ko/work/nfs_root/drivers_and_test/

(4)手动加载驱动程序


cat /proc/devices  :


insmod first_drv.ko


(5)编写驱动测试应用程序:


#include

#include

#include

#include

 

/* firstdrvtest on

  * firstdrvtest off

  */

int main(int argc, char **argv)

{

int fd;

int val = 1;

fd = open("/dev/xxx", O_RDWR);

if (fd < 0)

{

printf("can't open!n");

}

write(fd, &val, 4);

return 0;

}


arm-linux-gcc -ofirstdrvtest firstdrvtest.c


cp firstdrvtest/work/nfs_root/


挂载nfs服务器


mount -t nfs -o nolock192.168.1.102:/work/nfs_root /mnt




创建设备节点:


# mknod /dev/xxx   c    252   0     ---》/dev会出现xxx设备


ls /dev/



测试环境:


格式化nand,烧写光盘自带linux2.26、qt yaffs、uboot1.1.6,gcc:3.4.5.


四、思考与改进

(1)主设备号怎么定 


cat/proc/devices 找一个没用的     手工指定主设备号

写0,由系统分配                         自动分配主设备号

(2)dev/xxx怎么来的


应用程序


打开某个设备:open(dev/xxx)


首先要:


手工创建设备节点:mknod /dev/xxx c 252 0

但是当我们使用自动分配设备号的时候 ,我们都要cat /proc/devices查看主设备号名字,然后再创建设备节点吗?这样显然不合适。


自动创建设备节点,udex机制(mdev机制)

注册设备的时候,会在/sys下生成设备信息,


mdev:自动根据系统信息创建设备节点。


驱动程序里,提供设备信息,mdev就可以自动创建设备节点。


如何提供设备信息呢:

入口函数,先创建一个类—》类下面创建一个设备(提供系统信息)


class_create会在/sys下创建 firstdrv这个类,class_create类下创建xyz这个设备,mdev会自动创建一个dev/xyz设备节点


出口函数:

编译报错:

解决方法:

五、嵌入式linux   led字符设备驱动

前面写好了字符设备驱动程序框架,现在实现led字符设备驱动:

1、需要做什么

搭框架

完善硬件操作

(1)opne中实现端口输入输出配置


(2)入口函数:完成虚拟地址映射


(3)write中实现LED操作


copy_from_user 用户空间到内核空间传递数据。


copy_to_user();  内核空间到用户空间传递数据


2代码实现


(1)驱动完整代码:


#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

 

static struct class *firstdrv_class;

static struct class_device *firstdrv_class_dev;

 

volatile unsigned long *gpfcon = NULL;

volatile unsigned long *gpfdat = NULL;

 

 

static int first_drv_open(struct inode *inode, struct file *file)

{

//printk("first_drv_openn");

/* 配置GPF4,5,6为输出 */

*gpfcon &= ~((0x3<<(4*2)) | (0x3<<(5*2)) | (0x3<<(6*2)));

*gpfcon |= ((0x1<<(4*2)) | (0x1<<(5*2)) | (0x1<<(6*2)));

return 0;

}

 

static ssize_t first_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)

{

int val;

 

//printk("first_drv_writen");

 

copy_from_user(&val, buf, count); // copy_to_user();

 

if (val == 1)

{

// 点灯

*gpfdat &= ~((1<<4) | (1<<5) | (1<<6));

}

else

{

// 灭灯

*gpfdat |= (1<<4) | (1<<5) | (1<<6);

}

return 0;

}

 

static struct file_operations first_drv_fops = {

    .owner  =   THIS_MODULE,    /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */

    .open   =   first_drv_open,     

.write = first_drv_write,    

};

 

 

int major;

static int first_drv_init(void)

{

major = register_chrdev(0, "first_drv", &first_drv_fops); // 注册, 告诉内核

 

firstdrv_class = class_create(THIS_MODULE, "firstdrv");

 

firstdrv_class_dev = class_device_create(firstdrv_class, NULL, MKDEV(major, 0), NULL, "xyz"); /* /dev/xyz */

 

gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16);

gpfdat = gpfcon + 1;

 

return 0;

}

 

static void first_drv_exit(void)

{

unregister_chrdev(major, "first_drv"); // 卸载

 

class_device_unregister(firstdrv_class_dev);

class_destroy(firstdrv_class);

iounmap(gpfcon);

}

 

module_init(first_drv_init);

module_exit(first_drv_exit);

 

 

MODULE_LICENSE("GPL");


(2)编译程序,加载驱动

3 改进


当我们想实现对硬件的多种情况操作时:


(1)根据传入参数,执行多种操作


(2)创建多个设备节点,执行不同的硬件操作


代码参考:


drivers_and_testledsmyleds.c


/*

 * 执行insmod命令时就会调用这个函数 

 */

static int __init s3c24xx_leds_init(void)

//static int __init init_module(void)

 

{

    int ret;

int minor = 0;

 

    gpio_va = ioremap(0x56000000, 0x100000);

if (!gpio_va) {

return -EIO;

}

 

    /* 注册字符设备

     * 参数为主设备号、设备名字、file_operations结构;

     * 这样,主设备号就和具体的file_operations结构联系起来了,

     * 操作主设备为LED_MAJOR的设备文件时,就会调用s3c24xx_leds_fops中的相关成员函数

     * LED_MAJOR可以设为0,表示由内核自动分配主设备号

     */

    ret = register_chrdev(LED_MAJOR, DEVICE_NAME, &s3c24xx_leds_fops);

    if (ret < 0) {

      printk(DEVICE_NAME " can't register major numbern");

      return ret;

    }

 

leds_class = class_create(THIS_MODULE, "leds");

if (IS_ERR(leds_class))

return PTR_ERR(leds_class);

    

 

 

leds_class_devs[0] = class_device_create(leds_class, NULL, MKDEV(LED_MAJOR, 0), NULL, "leds"); /* /dev/leds */

for (minor = 1; minor < 4; minor++)  /* /dev/led1,2,3 */

{

leds_class_devs[minor] = class_device_create(leds_class, NULL, MKDEV(LED_MAJOR, minor), NULL, "led%d", minor);

if (unlikely(IS_ERR(leds_class_devs[minor])))

return PTR_ERR(leds_class_devs[minor]);

}

        

    printk(DEVICE_NAME " initializedn");

    return 0;

}



驱动测试应用程序:

#include

#include

#include

#include

 

/*

  *  ledtest

  */

 

void print_usage(char *file)

{

    printf("Usage:n");

    printf("%s n",file);

    printf("eg. n");

    printf("%s /dev/leds onn", file);

    printf("%s /dev/leds offn", file);

    printf("%s /dev/led1 onn", file);

    printf("%s /dev/led1 offn", file);

}

int main(int argc, char **argv)

{

    int fd;

    char* filename;

    char val;

 

    if (argc != 3)

    {

        print_usage(argv[0]);

        return 0;

    }

    filename = argv[1];

    fd = open(filename, O_RDWR);

    if (fd < 0)

    {

        printf("error, can't open %sn", filename);

        return 0;

    }

 

    if (!strcmp("on", argv[2]))

    {

        // 亮灯

        val = 0;

        write(fd, &val, 1);

[1] [2]
关键字:S3C2440  linux  字符设备驱动 引用地址:07-S3C2440驱动学习(一)嵌入式linux字符设备驱动-LED字符设备驱动

上一篇:07-S3C2440驱动学习(一)嵌入式linux字符设备驱动-按键驱动程序
下一篇:06-S3C2440学习之移植2012u-boot到S3C2440(移植过程五--最终)

推荐阅读最新更新时间:2024-11-09 10:39

基于OHCI协议的S3C2440 USB Host Driver之协议介绍(三)
OpenHCI通过两级仲裁在端点中选择哪一个服务。第一级是表级。每一个需要服务的端点类型在相应的类型表里。HC选择服务哪一个表。而在表内,端点都有差不多公平的机会被访问到。HCD通知设置HcPeriodicStart限制从帧开始到开始要服务周期表的时间。周期表服务时,当前帧下,中断表优先于同步表。当周期表服务完,控制和批量有可能会接着服务。 ED作为锚点,供一列TD依附在其上。当HC访问ED且找到一个有效的TD地址,HC完成与指定ED的单笔事务,TD表明了内存地址。 当一个TD所定义的所有数据都传输完,TD从他的ED上解下,且连到完成队列。 ED描述符 FA就是设备地址 EN就是端点地址 D为方向 S
[单片机]
基于OHCI协议的<font color='red'>S3C2440</font> USB Host Driver之协议介绍(三)
LCD实验学习笔记(八):中断
s3c2440有60个中断源(其中15个为子中断源)。 31个32位的通用寄存器,6个程序状态寄存器。有6种工作模式(系统/用户模式,快中断模式,管理模式,数据访问中止模式,中断模式,未定指令中止模式)。每种模式都有16个通用寄存器和1(或2)个程序状态寄存器。 R15(pc)是程序计数器,R14(lr)是连接寄存器,在异常时自动保存pc备份,r13(sp)是栈指针寄存器。 CPSR是当状程序状态寄存器。其 为控制位, 为中断禁止位, 为快中断禁止位, 为CPU状态位, 为工作模式位。, 程序状态寄存器(PSR)的F位 设为1,禁用快速中断(FRQ)。 程序状态寄存器(PSR)的I位 设为1,禁用普通中断(IRQ)。 SPSR程序
[单片机]
S3C2440移植linux3.4.2内核之内核裁剪
为什么要裁剪内核? 因为mtd的kernel分区只有2M大,而实际内核有2.37MB,所以需要裁剪到小于2M(或者修改mtd分区值) 首先裁剪内核里无关的CPU/单板文件 通过vi .config,然后搜索2440,如下图所示: 然后参考上图,make menuconfig 进入System Type --- SAMSUNG S3C24XX SoCs Support: 如上图所示,CPU下只选择2440,单板文件下只选择SMDK2440以及MINI2440相关 裁剪无关的文件系统ext2、ext3、ext4 重新make menuconfig,进入File systems,去掉: Second extende
[单片机]
<font color='red'>S3C2440</font>移植<font color='red'>linux</font>3.4.2内核之内核裁剪
S3C2440硬件编程实例
S3C2440硬件编程实例——GPIO(一)汇编程序: @****************************************************************************** @ 功能:LED点灯程序,点亮LED1-4 @****************************************************************************** .text .global _start _start: LDR R0,=0x56000010 @
[单片机]
<font color='red'>S3C2440</font>硬件编程实例
S3C2440时钟体系结构
任意一款单板,我们了解其时钟都是通过时钟树来的。 这里没有全部截完,只是讲解时钟来源,OSC代表晶振,这说明我们的时钟可以来至晶振OSC也可以来至外部输入EXTCLK,这是通过OM选择器来完成的。 2440的时钟,分为主时钟和usb时钟,先说主时钟,usb时钟之后用到的时候再说。 要如何配置FCLK,HCLK,PCLK? 可以知道我们cpu的最大时钟位400M,高速总线最大时钟136M,外设最大时钟68M。 为了试验,我们配置FCLK=400,HCLK=100,PCLK=50。 较之前的LED灯程序,我们增加一个初始化函数: 启动汇编增加一条语句: 这样下载进去之后,你会看到LED灯的闪烁频率变
[单片机]
<font color='red'>S3C2440</font>时钟体系结构
关于ARM9 S3C2440 wince6.0 内存 128M的使用问题
S3C2440 原先的时候接两块32M的SDRAM 片选都连接到nGCS6上。现在改成两块64M的SDRAM片选也是nGCS6上。 软件的修改分为四个大块: 1、 Stepldr: (1)startup.s中主要是 ldr r0,=0x30000000 ; Start address (physical 0x3000.0000). 一目了然,不再解释 (2)option.h #ifdef BSP_SDRAM_128M #define _ISR_STARTADDRESS 0x3
[单片机]
OK6410A 开发板 (八) 109 linux-5.11 OK6410A tmpfs 文件系统 CONFIG_SHMEM=n & CONFIG_TMPFS=n
drivers/base/devtmpfs.c devtmpfs 分为两种实现 1. 没有定义 CONFIG_TMPFS 2. 定义了 CONFIG_TMPFS 67 static struct file_system_type internal_fs_type = { 68 .name = devtmpfs , 69 #ifdef CONFIG_TMPFS 70 .init_fs_context = shmem_init_fs_context,
[单片机]
摄像头驱动学习
driversmediavideos3c2440camif.c driversmediavideos3c2440_ov9650.c driversmediavideosccb.c 硬件连接 I2C总线连接 I2CSCL ——GPE14 I2CSDA——GPE15 Sccb.h #define SIO_C S3C2410_GPE14 #define SIO_D S3C2410_GPE15 #define State(x) s3c2410_gpio_getpin(x) #define High(x) do{s3c2410_gpio_setpin(x,1); smp_mb();}while(0) #define Low(x) d
[单片机]
摄像头驱动学习

推荐帖子

【基于人脸识别的自动打卡健走计时系统】Maixbit/MaixPy 坑!音频播放函数阻塞调用
问题:阻塞循环播放音频文件,屏幕无法实时刷新摄像头采集的图像(有一段卡顿的画面)~~~尝试采用状态机一边播放,边刷新LCD,结果~~~播放的语音就不完整了(采集+LCD的时间大于44.1K播放调用的最小周期~~~)槽点:400MHz的CPU,400MHz的KPU~~~这样的效果,的确有点意外~~~究其原因,还是Sipeed/MaixPy~在micropython实现底层时,应该没有用DMA或者考虑多线程方式运行~~~,平台:Maixbit/MaixPy任务:
alanlan86 DigiKey得捷技术专区
MOTOROLA 单片机M68HC05 原理与应用大全
作者:刚寒冰,齐秋群主编出版社:北京理工大学出版社出版时间:1994页数:1006页 MOTOROLA单片机M68HC05原理与应用大全
wzt 单片机
问下2个光耦,EL357跟LTV354的区别
问下EL357跟LTV354的区别,能不能直接替换哦问下2个光耦,EL357跟LTV354的区别如果管脚封装一样,要看手册区别在哪里,应该可以互换吧是LTV-354T吧?两者除封装尺寸和引脚排列相同外,论细节谁都不敢说一定能互换。两者最大的差别就是,EL357是单向驱动,而LTV-354T是双向的,而且允许驱动电流低至1mA时,仍能保持有≧20%的CTR。
sky999 PCB设计
SM1191 FM-AM 收音机集成电路
概述SM1191是一款用在盒式收录机上的单片集成电路。特点◆消耗电流低(VCC=3V)FM:ID=5.3mA(Typ)AM:ID=3.4mA(Typ)◆内建FM/AM选择开关◆具有较大的AF放大输出VCC=6V,EIJAoutput=500mW(Tpy)当负载阻抗为8Ω时
rain 模拟电子
几个转换电路,,,,,
本帖最后由qwqwqw2088于2015-10-518:02编辑 此介绍3种电源转换器的设计原理图3.3V→5V电平转换器可以直接构成电平转换,往往是采用集成方案。有不同性能的电平转换器,有双向和单相配置、不同电压转换和不同速度的,用户根据需要选择最好的方案。器件间板级通信(如MCU到外设)往往靠SPI或I2C。对于SPI,采用单向电平转换器是合适的,而对于I2C,必须采用双向方案。图1说明了这两种方案。图1电平转换器3
qwqwqw2088 模拟与混合信号
有在深圳做WINCE开发吗?找兼职
有在深圳做WINCE开发吗?我公司现在准备找一个做这方面的人来给我们开发一个程序,兼职方式,联系QQ:396638838,邮件:jzy123791@163.com有在深圳做WINCE开发吗?找兼职 顶up
明枫 WindowsCE
小广播
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved