Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
carloscn opened this issue Apr 11, 2022 · 0 comments

Comments

@carloscn
Copy link
Owner

基于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 "\tRecv cmd: %u\n", cmd);
	printk(DRV_NAME "\tRecv arg: %lu\n", 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\n" , 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\n" , 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\n" , 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 "\tApply memory for AD9833.\n" );
	ad9833 = ad9833_dev_new();

	/*
	 * AD9833 init gpios.
	 * */
	printk( DRV_NAME "\tInititial GPIO\n" );

	for ( i = 0; i < 3; i ++ ) {
		ret	=	gpio_request( ad9833_gpios[i], "AD9833 GPIO" );
		if( ret ) {
			printk("\t%s: request gpio %d for AD9833 failed, ret = %d\n", DRV_NAME,ad9833_gpios[i],ret);
			return ret;
		}else {
			printk("\t%s: request gpio %d for AD9833 set succussful, ret = %d\n", 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 "\tinitialized\n" );
	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....\n");


    fd = open(dev_path, O_RDWR|O_NDELAY);  // 打开设备
    if (fd < 0) {
        printf("Can't open /dev/AD9833-ADI\n");
        return -1;
    }

    printf("open device.\n");

    if( strcmp(argv[1],"1") == 0 ) {
	ioctl(fd, CMD_TYPE_SIN, 5);
		printf("argc = %d,sine wave = %s \n", CMD_TYPE_SIN, argv[1]);
    }else if(  strcmp(argv[1],"2") == 0 ) {
		ioctl(fd, CMD_TYPE_TRI, 1);
		printf("argc = %d,tri wave = %s \n", CMD_TYPE_TRI,argv[1]);
    }else{
 		ioctl(fd, CMD_TYPE_SQE, 1);
		printf("argc = %d,sqe wave = %s \n", CMD_TYPE_SQE, argv[1]);
    }
    
    printf("argc = %d\n", 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内核

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant