???? 由于關于pcduino的資料比較少,所以這篇文章是參考了pcduino愛好者論壇的一篇教程《手把手教你用A10點燈》,并且系統的結合了linux驅動的開發步驟。讀完這篇文章,你不但可以對pcduino開發板的硬件結構有所了解,更重要的是可以對linux的驅動開發步驟有一個系統的認識。我也是一個linux驅動的新手,所以,寫的不對的地方,請大家指正。
???? 這一部分將會手把手教你創建一個Linux的驅動程序框架,在下一部分,我們只需要將控制pcduino硬件部分的代碼填入這個框架就可以了。像所有的應用程序都有一個main函數作為函數的入口一樣,linux驅動程序的入口是驅動的初始化函數。這個初始化函數是 module_init 來指定的,同樣,與初始化函數對應的驅動程序的退出函數是由? module_exit函數來指定的。下面就讓我們動手寫第一個版本的驅動程序吧。
#include <linux/module.h>
#include <linux/init.h>
static int __init led_init(void)
{printk("led init\n");return 0;
}static void __exit led_exit(void)
{printk("led exit\n");
}module_init( led_init );
module_exit( led_exit );
將上面代碼保存為 led.c,接下來就要編寫Makefile文件對剛剛編寫的驅動程序進行編譯了。新建Makefile文件,在里面輸入:
obj-m := led.o
all: make -C /usr/src/linux-headers-3.8.0-35-generic/ M=/home/asus/drive/
clean: rm *.o rm *.ko rm *.order rm *.symvers rm *.mod.c
uname -r
開發板 arduino?來進行查看。M 后面表示你的驅動所在的目錄。改好之后保存,注意,這個文件的名字一定得是? "Makefile"? 才行,make 和 rm命令前面一定是一個TAB符才行。輸入命令:
make
進行編譯,完成之后,使用ls查看,可以看到得到的文件如下:
built-in.o led.c led.ko led.mod.c led.mod.o led.o Makefile modules.order Module.symvers
sudo insmod led.ko
dmesg
sudo rmmod led.ko
開發版入門選擇什么板,
???? 上面寫的這個驅動程序是沒有什么作用的,在linux中,應用程序是通過設備文件來和驅動程序進行交互的。所以我們需要在驅動程序中建立設備文件,這個設備文件建立之后,就會存在于?? /dev/ ? 目錄下,應用程序就是通過對這個文件的讀寫,來向驅動程序發送命令,并通過驅動程序控制硬件的動作。每一個驅動程序對應著一個設備文件。要建立一個設備文件,首先必須擁有設備號才行,這個設備號就需要我們向linux系統提出申請,由linux系統為我們分配。設備號有主設備號和從設備號之分,主設備號使用來表示驅動的類型,從設備號表示使用同一個驅動的設備的編號,這里要申請的就是主設備號。使用?? alloc_chrdev_region?? 函數來申請一個設備號。設備號的類型為?? dev_t?? ,它是一個 32 位的數,其中 12 位用來表示主設備號,另外 20 位用來表示從設備號。可以使用?? MAJOR?? 宏和?? MINOR?? 宏來直接獲取主設備號和從設備號。我們第二個版本的程序如下:
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>//驅動名
#define DEV_NAME "led"
//從設備的個數
#define DEV_COUNT 1//聲明設備號
static dev_t dev_number;//初始化
static int __init led_init(void)
{//錯誤標記int err;printk("led init\n");//申請設備號err = alloc_chrdev_region(&dev_number,0,DEV_COUNT,DEV_NAME);if(err){printk("alloc device number fail\n");return err;}//如果申請成功,打印主設備號printk("major number : %d\n",MAJOR(dev_number));return 0;
}static void __exit led_exit(void)
{printk("led exit\n");//注銷申請的設備號unregister_chrdev_region(dev_number,DEV_COUNT);
}
[ 384.225850] led init
[ 384.225854] major number : 250
還可以使用命令?? cat? /proc/devices | grep? ‘led’? 查看獲得的設備號。
???? 設備號申請完畢后,就可以在?? /dev/?? 目錄下創建設備文件了。需要了解的是設備在內存中,使用結構體?? cdev?? 來表示,并且將我們申請的設備號,以及對文件操作的回調函數,統統的關聯起來。最后使用這個結構體,用函數?? class_create?? 和?? device_create?? 來創建一個設備文件。說了一下基本思路,還是先看程序吧:#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>//驅動名
#define DEV_NAME "led"
//從設備的個數
#define DEV_COUNT 1//三個回調函數,當在應用程序執行相應的操作時
//驅動程序會調用相應的函數來進行處理
ssize_t led_write(struct file *, const char __user *, size_t, loff_t *);
int led_open(struct inode *, struct file *);
int led_release(struct inode *, struct file *);//聲明設備號
static dev_t dev_number;
//設備在內存中表示的結構體
static struct cdev* cdevp;
//注冊文件操作的回調函數的結構體
static struct file_operations fops =
{.owner = THIS_MODULE,//注冊相應的回調函數.open = led_open,.release = led_release,.write = led_write,
};
//用來創建設備文件的class
static struct class* classp;//初始化
static int __init led_init(void)
{//錯誤標記int err;printk("led init\n");//申請設備號err = alloc_chrdev_region(&dev_number,0,DEV_COUNT,DEV_NAME); if(err){printk("alloc device number fail\n");return err;}//如果申請成功,打印主設備號printk("major number : %d\n",MAJOR(dev_number));//給cdev結構體在內存中分配空間cdevp = cdev_alloc();//如果分配失敗if( cdevp==NULL ){printk("cdev alloc failure\n");//注銷前面申請的設備號unregister_chrdev_region(dev_number,DEV_COUNT);return -1;}//將cdev結構體與//注冊文件操作的回調函數的結構體file_operations關聯起來cdev_init(cdevp,&fops);//將cdev結構體和申請的設備號關聯起來err = cdev_add(cdevp,dev_number,DEV_COUNT);if(err){printk("cdev add failure\n");//釋放申請的cdev空間cdev_del(cdevp);//注銷申請的設備編號unregister_chrdev_region(dev_number,DEV_COUNT);return err;}//給class分配空間classp = class_create(THIS_MODULE,DEV_NAME);if( classp==NULL ){printk("class create failure\n");//釋放申請的cdev空間cdev_del(cdevp);//注銷申請的設備編號unregister_chrdev_region(dev_number,DEV_COUNT);return -1;}//創建設備文件device_create(classp,NULL,dev_number,"%s",DEV_NAME);printk("/dev/%s create success\n",DEV_NAME);return 0;
}static void __exit led_exit(void)
{printk("led exit\n");//釋放分配的class空間if( classp ){device_destroy(classp,dev_number);class_destroy(classp);}//釋放分配的cdev空間if( cdevp ){cdev_del(cdevp);}//注銷申請的設備號unregister_chrdev_region(dev_number,DEV_COUNT);
}module_init( led_init );
module_exit( led_exit );//當在應用程序中執行 open 函數時,
//會調用下面的這個函數
int led_open(struct inode* pinode,struct file* pfile)
{printk("led open\n");return 0;
}//當在應用程序中執行 close 函數時,
//會調用下面的函數
int led_release(struct inode* pinode,struct file* pfile)
{printk("led release\n");return 0;
}//當在應用程序中調用 write 函數時,
//會調用下面的這個函數
ssize_t led_write(struct file* pfile,const char __user* buf,size_t count,loff_t* l)
{printk("led write");return 0;
}//指定采用的協議
MODULE_LICENSE("GPL");
insmod: error inserting 'led.ko': -1 Unknown symbol in module
這個錯誤。編譯,安裝好,之后,我們就可以在 ? /dev/ ? 目錄下找到?? led ?? 文件,使用命令:
ls -l /dev/led
crw------- 1 root root 250, 0 Dec 26 10:52 /dev/led
led燈板能貼圖嗎?
???? 至此我們的linux設備驅動框架,已經完全建立起來了。接下來要做的工作,就是對?? pcduino?? 開發板進行編程了。
???? 所使用的開發板是pcduino開發板,如下圖:
這是一款開源硬件,采用的是cortex-A8的核心,板上可以安裝ubuntu,android系統,我們使用的板子已經安裝了?? ubuntu?? 系統,通過?? HDMI轉VGA?? 線連接屏幕,并且通過usb接口,連接鍵盤和鼠標,直接在其自帶的ubuntu系統上,編寫驅動并運行。我們仔細的查看板子,會發現板上一共帶有 3 個led燈,分別是? RX_LED,TX_LED,ON_LED,分別用來指示接收,發送和電源的狀態。這里我們只控制? TX_LED? 燈進行閃爍。查看? pcduino? 的硬件原理圖,查找? TX_LED? 的連接位置,如下圖:
led燈板怎么換。會看到第三行?? TX_LED?? 連接到? CPU? 的PH15引腳,并且? L? 即低電平時為激活狀態,H 高電平時,為熄滅狀態。得到這個信息說明,我們只需要控制? CPU? 的引腳? PH15? 的狀態,就可以控制? TX_LED? 的狀態了。
???? 所以接下來就需要我們去查看? A10 的芯片手冊,來看一看到底怎么控制? PH15? 這個引腳。
可以看到? A10? 芯片的引腳有很多,而我們只關注? PH,因為我們要控制的就是? PH15? 這個引腳。這里需要的一個概念就是,對一個引腳的控制至少需要有兩個寄存器,一個是控制寄存器,一個是數據寄存器。控制寄存器用來控制引腳的工作模式,比如輸出或者輸入;數據寄存器用來向引腳輸出數據或者從引腳讀入數據。所以我們要先查看一下? PH15? 的配置寄存器,如下圖:
開發板控制led燈。我們發現?? PH15?? 控制寄存器一共有3位28-30,共有 8 種工作模式,由于要控制 led 的狀態,我們將它設置為輸出模式,所以? PH15? 控制寄存器的內容應該為 001。那么這個寄存器在哪個位置呢,在表上有?? Offset:0x100?? 我們知道,PH寄存器的偏移地址是? 0x100,但是基地址是多少呢。再往前面查閱就會發現
所以基地址就是? 0x01C20800。基地址和偏移地址都有了,我們就可以定位? PH_CFG1? 寄存器的地址就是(0x01C20800+0x100),我們只需要將這個寄存器的第28-30位置為:
30 29 28
0 0 1
???? 當控制寄存器配置完成之后,我們就需要向數據寄存器寫入數據來控制? led? 的閃爍。我們同樣查看芯片手冊:
可以看到,PH的數據寄存器用每一位來表示一個引腳的狀態。我們要控制? PH15 引腳,就需要對這個寄存器的第15位進行操作。所以,接下來就是,開始動手向驅動框架中添加對硬件操作的時候:
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <asm/io.h>
#include <asm/uaccess.h>//驅動名
#define DEV_NAME "led"
//從設備的個數
#define DEV_COUNT 1//定義與硬件相關的宏
//基地址
#define BASE_ADDRESS 0x01C20800
//PH_CFG1寄存器的地址
#define PH_CFG1 (BASE_ADDRESS+0x100)
//PH_DAT寄存器的地址
#define PH_DAT (BASE_ADDRESS+0x10C)//三個回調函數,當在應用程序執行相應的操作時
//驅動程序會調用相應的函數來進行處理
ssize_t led_write(struct file *, const char __user *, size_t, loff_t *);
int led_open(struct inode *, struct file *);
int led_release(struct inode *, struct file *);//聲明設備號
static dev_t dev_number;
//設備在內存中表示的結構體
static struct cdev* cdevp;
//注冊文件操作的回調函數的結構體
static struct file_operations fops =
{.owner = THIS_MODULE,//注冊相應的回調函數.open = led_open,.release = led_release,.write = led_write,
};
//用來創建設備文件的class
static struct class* classp;//聲明用來表示PH_CFG1內存地址的變量
volatile static unsigned long* __ph_cfg1;
//用來表示PH_DAT內存地址的變量
volatile static unsigned long* __ph_dat;//初始化
static int __init led_init(void)
{//錯誤標記int err;printk("led init\n");//申請設備號err = alloc_chrdev_region(&dev_number,0,DEV_COUNT,DEV_NAME); if(err){printk("alloc device number fail\n");return err;}//如果申請成功,打印主設備號printk("major number : %d\n",MAJOR(dev_number));//給cdev結構體在內存中分配空間cdevp = cdev_alloc();//如果分配失敗if( cdevp==NULL ){printk("cdev alloc failure\n");//注銷前面申請的設備號unregister_chrdev_region(dev_number,DEV_COUNT);return -1;}//將cdev結構體與//注冊文件操作的回調函數的結構體file_operations關聯起來cdev_init(cdevp,&fops);//將cdev結構體和申請的設備號關聯起來err = cdev_add(cdevp,dev_number,DEV_COUNT);if(err){printk("cdev add failure\n");//釋放申請的cdev空間cdev_del(cdevp);//注銷申請的設備編號unregister_chrdev_region(dev_number,DEV_COUNT);return err;}//給class分配空間classp = class_create(THIS_MODULE,DEV_NAME);if( classp==NULL ){printk("class create failure\n");//釋放申請的cdev空間cdev_del(cdevp);//注銷申請的設備編號unregister_chrdev_region(dev_number,DEV_COUNT);return -1;}//創建設備文件device_create(classp,NULL,dev_number,"%s",DEV_NAME);printk("/dev/%s create success\n",DEV_NAME);return 0;
}static void __exit led_exit(void)
{printk("led exit\n");//釋放分配的class空間if( classp ){device_destroy(classp,dev_number);class_destroy(classp);}//釋放分配的cdev空間if( cdevp ){cdev_del(cdevp);}//注銷申請的設備號unregister_chrdev_region(dev_number,DEV_COUNT);
}module_init( led_init );
module_exit( led_exit );//當在應用程序中執行 open 函數時,
//會調用下面的這個函數
int led_open(struct inode* pinode,struct file* pfile)
{//臨時變量unsigned long tmp; printk("led open\n");//將PH15管腳設置為輸出狀態//將PH_CFG1這個硬件寄存器的地址,映射到linux內存,并獲取映射后的地址//通過對這個地址的操作,就可以控制PH_CFG1__ph_cfg1 = (volatile unsigned long*)ioremap(PH_CFG1,4);//將設置PH15寄存器tmp = *__ph_cfg1;tmp &= ~(0xf<<28);tmp |= (1<<28);*__ph_cfg1 = tmp;//將燈初始化為熄滅的狀態__ph_dat = (volatile unsigned long*)ioremap(PH_DAT,4);tmp = *__ph_dat;tmp |= (1<<15);*__ph_dat = tmp; return 0;
}//當在應用程序中執行 close 函數時,
//會調用下面的函數
int led_release(struct inode* pinode,struct file* pfile)
{printk("led release\n");//注銷分配的內存地址iounmap(__ph_dat);iounmap(__ph_cfg1);return 0;
}//當在應用程序中調用 write 函數時,
//會調用下面的這個函數
ssize_t led_write(struct file* pfile,const char __user* buf,size_t count,loff_t* l)
{int val;volatile unsigned long tmp;printk("led write\n");//從用戶空間讀取數據copy_from_user(&val,buf,count); printk("write %d\n",val);//從應用程序讀取命令//來控制led燈tmp = *__ph_dat;if( val==1 ){//燈亮tmp &= ~(1<<15); }else{//燈滅tmp |= (1<<15);}*__ph_dat = tmp;return 0;
}MODULE_LICENSE("GPL");
32開發板。
???? 上面的是完整的控制pcduino上led閃爍的驅動程序,寫完這個驅動程序之后,再寫一個下面的測試程序就可以使 led 閃爍了,測試的代碼如下:
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>int main(void)
{int fd;int val = 1;//打開驅動對應的設備文件fd = open("/dev/led",O_RDWR);if( fd<0 ){printf("open /dev/led error\n");return -1;}while(1){//寫入高電平write(fd,&val,sizeof(int));//睡眠一秒sleep(1);//將電平反轉val = 0;//寫入低電平write(fd,&val,sizeof(int));//睡眠一秒sleep(1);val = 1;}close(fd);return 0;
}
sudo ./a.out
版权声明:本站所有资料均为网友推荐收集整理而来,仅供学习和研究交流使用。
工作时间:8:00-18:00
客服电话
电子邮件
admin@qq.com
扫码二维码
获取最新动态