基於OMAPL138的Linux字符驅動_GPIO驅動AD9833(一)之miscdevice和ioctl

基於OMAPL138的Linux字符驅動_GPIO驅動AD9833(一)之miscdevice和ioctl

0. 導語

在嵌入式的道路上尋尋覓覓好久,進入嵌入式這個行業也有幾年的時間了,從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_XMzY3NDUwNTMwOA==.html?spm=a2h3j.8428770.3416059.1shell

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進行。ui

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

能夠看見,在miscdev裏面指定了file指針的地址,miscdev主要的做用就是向內核註冊該驅動spa

*函數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內核

相關文章
相關標籤/搜索