• 基于OMAPL138的Linux字符驱动_GPIO驱动AD9833(一)之miscdevice和ioctl


    基于OMAPL138的Linux字符驱动_GPIO驱动AD9833(一)之miscdevice和ioctl

    0. 导语

    在嵌入式的道路上寻寻觅觅很久,进入嵌入式这个行业也有几年的时间了,从2011年后半年开始,我清楚的记得当时拿着C51的板子闪烁了LED灯,从那时候开始,就进入到了嵌入式的大门里面。嵌入式的学习从来没有停止过,中间也有无数的插曲和机缘巧合学会C++和Java,做一些好玩的应用。无论是嵌入式DSP也好,还是如今的嵌入式ARM,7年之久从来没有停止过。技术最大的好处就是,无论发展到什么境地,那种第一次点亮LED灯欣喜永远的可以伴随着你,只要你解决了一个卡了你很久的问题,这就是技术的魅力。我也将开始大肆的从嵌入式DSP转入到嵌入式Linux,在研究生阶段,完成这个转型。

    这个Demo意义重大,使用Linux也有四五年的时间了,Linux良好的基础和嵌入式基础让我在嵌入式inux道路上算的上是顺风顺水。**这个Demo将过去STM32,F28xx的DSP或者那些单片机桥接起来,将过去裸机上的程序全部编到内核里面,通过嵌入式的应用进行互联。 **

    本DEMO依然使用AD9833作为例子,将用linux内核级的gpio对AD9833写时序,完成对于AD9833的驱动程序,在嵌入式Linux上生成/dev/目录节点,使用Linux命令行对AD9833产生波形进行控制。(只要有了/dev节点,使用Qt,C++,Python都可以控制了,这就是物联网最注重的。)

    效果视频观看地址: https://v.youku.com/v_show/id_XMzY3NDUwNTMwOA==.html?spm=a2h3j.8428770.3416059.1

    1. 开发驱动综述

    本开发驱动基于Linux3.3内核版本,且内核必须编译正确,否则不能运行。
    这个Demo可以归结为三个部分,一个部分为Linux字符驱动模板,第二部分为AD9833驱动程序,第三部分为通信协议。还附加一个配置文件。

    • Linux字符驱动模板主要包含init exit 还有ioctl,函数;
    • AD9833驱动程序为AD9833的GPIO时序(AD9833为SPI协议,这里先用GPIO模拟时序,后续升级为SPI外设);
    • 通信协议格式方式,用户对于AD9833的控制字,比如发送波形命令,频率命令等;
    • 将自己编写的驱动写入内核的代码树,编译成模块或者编译进内核随内核启动;

    本Demo就围绕这三点进行。

    2. Linux字符驱动模板

    * 函数ioctl

    主要负责进行数据交互的。当设备生成字符设备驱动节点(/dev目录下),使用shell级命令cat或者编译一段C应用程序用open打开节点的时候,后面将参数就是通过ioctl函数进行传递。(在嵌入式Linux端定义一个ioctl的函数,在C语言的程序也有一个ioctl用来和其进行对应,这样就完成了数据参数传递。)

    *结构体file_operations

    static int
    ad9833_ioctl(struct file  *file, unsigned int cmd, unsigned long arg )
    {
    
    	printk(DRV_NAME "	Recv cmd: %u
    ", cmd);
    	printk(DRV_NAME "	Recv arg: %lu
    ", arg);
    	switch( cmd ) {
    	case CMD_TYPE_SIN:
    		ad9833->set_wave_freq(ad9833, 1500);
    		ad9833->set_wave_type(ad9833, SIN);
    		printk( DRV_NAME " set wave is sine wave! arg = %lu
    " , arg );
    
    		break;
    
    	case CMD_TYPE_TRI:
    		ad9833->set_wave_freq(ad9833, 1500);
    		ad9833->set_wave_type(ad9833, TRI);
    		printk( DRV_NAME " set wave is tri wave! arg = %lu
    " , arg );
    		break;
    
    	case CMD_TYPE_SQE:
    		ad9833->set_wave_freq(ad9833, 1500);
    		ad9833->set_wave_type(ad9833, SQU);
    		printk( DRV_NAME " set wave is sw wave! arg = %lu
    " , arg );
    		break;
    
    	}
    	return	0;
    }
    

    ioctl函数不能独立的存在需要file_operations指针进行操作,ioctl为一个执行命令的清单,file_operations就是这个清单的执行者。下面就是file_operations的指针,里面的成员需要接收到ad9833_ioctl的函数地址,在内部运行的时候会调用该地址。

    static struct file_operations ad9833_fops = {
    
    		.owner				=	THIS_MODULE,
    		.unlocked_ioctl  	=  	ad9833_ioctl,
    };
    

    * 结构体miscdevice

    *miscdevice结构体为字符驱动的一级,字符驱动如同文献[3]所说的一样,非常的凌乱,到底里面使用了miscdevice还是cdev还是platform-device or platform-driver,这里暂时不进行理,这里使用miscdevice级的字符驱动设备向Linux内核进行设备的注册,后续有文章进行区分,类似的文献还有我的《Linux GPIO键盘驱动开发记录_OMAPL138》,这里使用的室platform-device进行。

    static struct miscdevice ad9833_miscdev  = {
    		// DRV_NAME 在前面进行define
    		// #define	DRV_NAME 	"AD9833-ADI"
    		.name				=	DRV_NAME,
    		.fops				=	&ad9833_fops,
    };
    

    可以看见,在miscdev里面指定了file指针的地址,miscdev主要的作用就是向内核注册该驱动

    *函数init

    内核级的嵌入式Linux驱动给出的硬性要求进行init函数,并标识init函数为__init,而且还要在module_init中填写init函数的地址。

    static int __init ad9833_dev_init( void )
    {
    	int  i,ret;
    
    	/*
    	 * AD9833 new device
    	 * */
    	printk( DRV_NAME "	Apply memory for AD9833.
    " );
    	ad9833 = ad9833_dev_new();
    
    	/*
    	 * AD9833 init gpios.
    	 * */
    	printk( DRV_NAME "	Inititial GPIO
    " );
    
    	for ( i = 0; i < 3; i ++ ) {
    		ret	=	gpio_request( ad9833_gpios[i], "AD9833 GPIO" );
    		if( ret ) {
    			printk("	%s: request gpio %d for AD9833 failed, ret = %d
    ", DRV_NAME,ad9833_gpios[i],ret);
    			return ret;
    		}else {
    			printk("	%s: request gpio %d for AD9833 set succussful, ret = %d
    ", DRV_NAME,ad9833_gpios[i],ret);
    		}
    		gpio_direction_output( ad9833_gpios[i],1 );
    		gpio_set_value( ad9833_gpios[i],0 );
    	}
    
    	ret = misc_register( &ad9833_miscdev );
    	printk( DRV_NAME "	initialized
    " );
    	return ret;
    }
    
    module_init( ad9833_dev_init );
    

    当我们运行insmod xxxx.ko的时候,此时运行的就是这个init函数,在这个函数中主要完成对于设备内存的请求和一些初始状态的注册。在本DEMO中对对于AD9833的结构体进行了注册,并对gpio进行申请。ret = misc_register( &ad9833_miscdev ); 重点室这句话。

    *函数exit

    除此之外内核也要求了exit函数,主要进行对init中内存申请的释放。

    static void __exit ad9833_dev_exit( void )
    {
    	int i;
    	for( i = 0; i < 3; i++) {
    		gpio_free( ad9833_gpios[i] );
    	}
    	misc_deregister( &ad9833_miscdev );
    
    }
    module_exit( ad9833_dev_exit );
    

    这是一个非常简单的字符驱动的模板,然后就需要我们添加AD9833的驱动了。

    3. AD9833芯片级时序驱动

    到此,基本上就是裸机嵌入式的知识了,对于芯片功能的描述,对于芯片时序的把握。作为本博客不在赘述,给出函数的列表,如果喜欢,本文将DEMO的源码放在后面,自行下载观看。

    static void ad9833_set_wave_type( AD9833 *dev, enum ad9833_wavetype_t wave_type );
    static void ad9833_set_phase( AD9833 *dev, unsigned int phase_value );
    static void ad9833_set_freq( AD9833 *dev, float freq );
    static void ad9833_set_para( AD9833 *dev, unsigned long freqs_value, unsigned int phase_value, enum ad9833_wavetype_t wave_type );
    static void ad9833_init_device( AD9833 *dev ) ;
    static void ad9833_write_reg( AD9833 *dev, unsigned int reg_value );
    static int 	ad9833_ioctl(struct file  *file, unsigned int cmd, unsigned long arg );
    AD9833 *ad9833;
    
    AD9833 *ad9833_dev_new()
    {
    	AD9833 *dev = (AD9833*)kcalloc(1, sizeof(AD9833), GFP_ATOMIC);
    
    	dev->hw.fsy			  =	  AD9833_FSY_IO;
    	dev->hw.sdi			  =   AD9833_DAT_IO;
    	dev->hw.clk			  =	  AD9833_CLK_IO;
    
    	dev->set_wave_para    =   &ad9833_set_para;
    	dev->init_device      =   &ad9833_init_device;
    	dev->write_reg        =   &ad9833_write_reg;
    	dev->set_wave_freq    =   &ad9833_set_freq;
    	dev->set_wave_phase	  =   &ad9833_set_phase;
    	dev->set_wave_type    =   &ad9833_set_wave_type;
    	dev->init_device( dev );
    
    
    	return dev;
    }
    

    该设备使用链表进行描述。

    3. 与驱动通信的ioctl函数

    在参考文献[1]中,给出了Linux字符设备驱动开发重要的ioctl函数解析,写的很接地气,很朴实,也写的很明白,包括利用ioctl函数应用程序和驱动程序进行交互,ioctl函数使用MAGIC_number幻数对命令进行转换。
    在ioctrl函数里面通常使用switch 和case进行执行,见上衣章的内容。
    这里给出使用ioctl的应用程序,它和内核驱动进行通信:

    
    #include <stdlib.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/ioctl.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <string.h>
    
    #define				AD9833_MAGIC				'k'
    #define				CMD_TYPE_SIN				_IO( AD9833_MAGIC, 0)
    #define				CMD_TYPE_TRI				_IO( AD9833_MAGIC, 1)
    #define				CMD_TYPE_SQE				_IO( AD9833_MAGIC, 2)
    
    
    const char dev_path[]="/dev/AD9833-ADI";
    
    int main(int argc , char *argv[])
    {
    
        int fd = -1, i = 0;
        printf("ad9833 test program run....
    ");
    
    
        fd = open(dev_path, O_RDWR|O_NDELAY);  // 打开设备
        if (fd < 0) {
            printf("Can't open /dev/AD9833-ADI
    ");
            return -1;
        }
    
        printf("open device.
    ");
    
        if( strcmp(argv[1],"1") == 0 ) {
    	ioctl(fd, CMD_TYPE_SIN, 5);
    		printf("argc = %d,sine wave = %s 
    ", CMD_TYPE_SIN, argv[1]);
        }else if(  strcmp(argv[1],"2") == 0 ) {
    		ioctl(fd, CMD_TYPE_TRI, 1);
    		printf("argc = %d,tri wave = %s 
    ", CMD_TYPE_TRI,argv[1]);
        }else{
     		ioctl(fd, CMD_TYPE_SQE, 1);
    		printf("argc = %d,sqe wave = %s 
    ", CMD_TYPE_SQE, argv[1]);
        }
        
        printf("argc = %d
    ", argc);
        close(fd);
        return 0;
    }
    
    

    在ioctl函数和嵌入式Linux驱动里面的ioctl函数就会对应,命令就传递过去了。

    另外补充一个知识:

    void main( int argc char *argv[] )

    • argc 为传递参数的个数
    • argv[1] 为一个字符串,第一个传递进来的字符串,比如 ./main.o nihao hello 1234
      argv[1] 就是nihao, argv[2] 就是hello, argv[3] 就是1234

    4. 将驱动程序编入Linux内核代码树

    驱动开发完毕,就必须要将驱动编入Linux内核代码树,假如Linux内核代码在./linux-3.3目录,我们的驱动名字叫做ad9833.c,那么我们就要将ad9833.c文件放入./linux-3.3/drivers/char目录下,操作两件事情。

    修改Kconfig文件

    修改Kconfig文件,在menuconfig文件中会出现我们的内核配置选项。

    config  AD9833_ADI
            tristate "AD9833 DDS support."
            depends on ARM
            help
              GPIO on OMAPL138 configuration is:
              AD9833_FSY_IO -> GPIO[0,1]
              AD9833_CLK_IO -> GPIO[0,5]
              AD9833_DAT_IO -> GPIO[0,0]
    
    • tristate: 内核在linux menuconfig菜单下显示的名字
    • depends on ARM: 只有在ARM架构下才会显示出来该驱动于menuconfig中
    • help :帮助文档,做一些提示,我这里给出了GPIO的接法。

    修改该目录下的Makefile文件

    在文末追加
    obj-$(CONFIG_AD9833_ADI) += ad9833.o
    这里CONFIG_后面接的必须和上面的Kconfig中 config字段一样 ad9833.o 的.o文件必须和放入该内核代码的ad9833.c名字字段一样。

    编译内核

    • 配置menuconfig
      make CROSS_COMPILE=arm-none-linux-gnueabi- ARCH=arm menuconfig
      然后,进入到drivers -> char.. device -> 找到你的驱动
      以模块编译或者编译进内核。
    • 编译内核
      make CROSS_COMPILE=arm-none-linux-gnueabi- ARCH=arm -j8
    • 生成uImage文件 (这个是omapl平台要求的)
      make CROSS_COMPILE=arm-none-linux-gnueabi- ARCH=arm uImage
    • 将内核和文件都放到目标板子
      可以重启运行了
    • 加载内核
      insmod ad9833.ko
    • 运行测试程序
      可以看到效果了:

    源代码下载

    链接: https://pan.baidu.com/s/1rfZymtf-mRnZNlhb41RpGA 密码: 4pxx

    参考文献

    [1] zqixiao_09, Linux 字符设备驱动开发基础(四)—— ioctl() 函数解析
    , 2016-03-11
    [2] 草根老师, 解决undefined reference to __aeabi_uidivmod和undefined reference to __aeabi_uidiv'错误, 2012-07-21 21:59:03
    [3] 小C爱学习, 一步一步写miscdevice的驱动模块, 2013-07-24
    [4] 宋宝华,Linux设备驱动开发详解:基于最新的Linux 4.0内核

  • 相关阅读:
    优化-UITableView性能
    优化-预渲染加速iOS设备的图像显示
    UIWebView
    NSJSONSerialization
    UITableView UITableViewCell NSIndexPath
    NSDictionary NSMutableDictionary
    iOS Delegate NSNotificationCenter
    Php解决跨域名共享session方案整理专题
    memached共享session
    二级域名 session共享
  • 原文地址:https://www.cnblogs.com/sigma0/p/9201572.html
Copyright © 2020-2023  润新知