在嵌入式的道路上寻寻觅觅好久,进入嵌入式这个行业也有几年的时间了,从2011年后半年开始,我清楚的记得当时拿着C51的板子闪烁了LED灯,从那时候开始,就进入到了嵌入式的大门里面。嵌入式的学习历来没有中止过,中间也有无数的插曲和机缘巧合学会C++和Java,作一些好玩的应用。不管是嵌入式DSP也好,仍是现在的嵌入式ARM,7年之久历来没有中止过。技术最大的好处就是,不管发展到什么境地,那种第一次点亮LED灯欣喜永远的能够伴随着你,只要你解决了一个卡了你好久的问题,这就是技术的魅力。我也将开始大肆的从嵌入式DSP转入到嵌入式Linux,在研究生阶段,完成这个转型。html
这个Demo意义重大,使用Linux也有四五年的时间了,Linux良好的基础和嵌入式基础让我在嵌入式inux道路上算的上是顺风顺水。这个Demo将过去STM32,F28xx的DSP或者那些单片机桥接起来,将过去裸机上的程序所有编到内核里面,经过嵌入式的应用进行互联。 linux
本DEMO依然使用AD9833做为例子,将用linux内核级的gpio对AD9833写时序,完成对于AD9833的驱动程序,在嵌入式Linux上生成/dev/目录节点,使用Linux命令行对AD9833产生波形进行控制。(只要有了/dev节点,使用Qt,C++,Python均可以控制了,这就是物联网最注重的。)ios
效果视频观看地址: https://v.youku.com/v_show/id...shell
本开发驱动基于Linux3.3内核版本,且内核必须编译正确,不然不能运行。
这个Demo能够归结为三个部分,一个部分为Linux字符驱动模板,第二部分为AD9833驱动程序,第三部分为通讯协议。还附加一个配置文件。架构
本Demo就围绕这三点进行。函数
主要负责进行数据交互的。当设备生成字符设备驱动节点(/dev目录下),使用shell级命令cat或者编译一段C应用程序用open打开节点的时候,后面将参数就是经过ioctl函数进行传递。(在嵌入式Linux端定义一个ioctl的函数,在C语言的程序也有一个ioctl用来和其进行对应,这样就完成了数据参数传递。)学习
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结构体为字符驱动的一级,字符驱动如同文献[3]所说的同样,很是的凌乱,到底里面使用了miscdevice仍是cdev仍是platform-device or platform-driver,这里暂时不进行理,这里使用miscdevice级的字符驱动设备向Linux内核进行设备的注册,后续有文章进行区分,相似的文献还有个人《Linux GPIO键盘驱动开发记录_OMAPL138》,这里使用的室platform-device进行。ui
static struct miscdevice ad9833_miscdev = { // DRV_NAME 在前面进行define // #define DRV_NAME "AD9833-ADI" .name = DRV_NAME, .fops = &ad9833_fops, };
能够看见,在miscdev里面指定了file指针的地址,miscdev主要的做用就是向内核注册该驱动。spa
内核级的嵌入式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函数,主要进行对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的驱动了。
到此,基本上就是裸机嵌入式的知识了,对于芯片功能的描述,对于芯片时序的把握。做为本博客不在赘述,给出函数的列表,若是喜欢,本文将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; }
该设备使用链表进行描述。
在参考文献[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[] )
argv[1] 就是nihao, argv[2] 就是hello, argv[3] 就是1234
驱动开发完毕,就必需要将驱动编入Linux内核代码树,假如Linux内核代码在./linux-3.3目录,咱们的驱动名字叫作ad9833.c,那么咱们就要将ad9833.c文件放入./linux-3.3/drivers/char目录下,操做两件事情。
修改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]
在文末追加obj-$(CONFIG_AD9833_ADI) += ad9833.o
这里CONFIG_后面接的必须和上面的Kconfig中 config字段同样 ad9833.o 的.o文件必须和放入该内核代码的ad9833.c名字字段同样。
make CROSS_COMPILE=arm-none-linux-gnueabi- ARCH=arm menuconfig
而后,进入到drivers -> char.. device -> 找到你的驱动
以模块编译或者编译进内核。
make CROSS_COMPILE=arm-none-linux-gnueabi- ARCH=arm -j8
make CROSS_COMPILE=arm-none-linux-gnueabi- ARCH=arm uImage
能够重启运行了
insmod ad9833.ko
能够看到效果了:
连接: https://pan.baidu.com/s/1rfZy... 密码: 4pxx
[1] zqixiao_09, [Linux 字符设备驱动开发基础(四)—— ioctl() 函数解析
](https://blog.csdn.net/zqixiao... 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内核