在嵌入式的道路上尋尋覓覓好久,進入嵌入式這個行業也有幾年的時間了,從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
本開發驅動基於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 *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[] )
驅動開發完畢,就必需要將驅動編入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/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內核