1. 程式人生 > >3.修改第一個程序來點亮LED

3.修改第一個程序來點亮LED

只需要 mdev bre inode map() printf 創建 kde unsigned

在上一節中已經將驅動程序框架搭建好了

接下來開始寫硬件的操作(控制LED):

(1)看原理圖,確定引腳

(2)看2440手冊

(3)寫代碼(需要使用ioremap()函數映射虛擬地址,在linux中只能使用虛擬地址)

(4)修改上一節的測試程序

(5)使用次設備號來控制設備下不同的燈

1.看led引腳

技術分享

最終確定: LED1 ->GPF4 LED2 ->GPF5 LED3 ->GPF6

2.看2440手冊

配置GPFCON[15:0](0x56000050)的位[8:9]、位[10:11]、位[12:13] 都等於0x01(輸出模式)

控制GPFDAT[7:0](0x56000054)中的位4~6來使燈亮滅(低電平亮)

3.寫代碼

3.1添加全局變量:

volatile unsigned long *GPFcon=NULL;       

volatile unsigned long *GPFdat=NULL;

3.2 first_drv_init入口函數中使用ioremap()映射虛擬地址:

GPFcon = ioremap(0x56000050, 16);   //ioremap:物理地址映射,返回虛擬地址

GPFdat=GPFcon+1;             //long:32位,所以GPFdat=0x56000050+(32/8)

3.3 first_drv_exit出口函數中註銷虛擬地址:

iounmap(GPFcon);          //
註銷虛擬地址

3.4 first_drv_open函數中添加配置GPFCON:

*GPFcon&=~ ((0X11<<8)| (0X11<<10)| (0X11<<12)); 

*GPFcon|=    ((0X01<<8)| (0X01<<10)| (0X01<<12)); 

3.5 first_drv_write函數中添加拷貝應用層數據,然後來控制GPFDAT:

/*copy_to_user():將數據上給用戶*/
copy_from_user(&val,buf,count);      //從用戶(應用層)拷貝數據                                                            
if(val==1) //點燈(低電平亮) { *GPFdat&=~((0X1<<4)| (0X1<<5)| (0X1<<6)); } else //滅燈 { *GPFdat|=((0X1<<4)| (0X1<<5)| (0X1<<6)); }

4.修改測試程序main()

代碼如下:

int main(int argc,char **argv) //argc:參數個數,argv數組
{
int fd1, fd2;
int val=1;
fd1 = open("/dev/xyz",O_RDWR);  //打開/dev/xxx設備節點
if(fd1<0)                   //無法打開,返回-1
  printf("can‘t open%d!\n", fd1); 
   if(argc!=2)
     {
            printf("Usage:\n");
            printf("%s <on|off>",argv[0]);
            return 0;
     }

  if(strcmp(argv[1],"on")==0)   //開燈
      {
          printf("led on...\n");
          val=1;
      } 
  else                         //關燈
      {
          printf("led off...\n");
          val=0;
      }

write(fd1, &val, 4);
return 0;
}

當輸入first_driver_text on點3個燈, 否則關3個燈

若參數不等於2時,不能控制點燈

如果我們想分別控制不同的燈,該怎麽做?

可以使用此設備號,此設備號就是用來區分同一設備下不同子設備

5使用次設備號來控制設備下不同的燈

我們先來看下面兩個函數MAJOR和MINOR,分別是提取主次設備號

minor=MINOR(inode->i_rdev);    //open函數中提取次設備號
major=MAJOR(inode->i_rdev);    //open函數中提取主設備號

minor=MINOR (file->f_dentry->d_inode->i_rdev);  //write/read函數中提取次設備號
major= MAJOR (file->f_dentry->d_inode->i_rdev); //write/read函數中提取主設備號

思路如下:

在測試程序中:

通過dev[1]來open打開不同的子設備節點,然後通過dev[2]來write寫入數據

實例: first_driver_text led1 on //點亮led1

在first_dev.c驅動文件中:

first_drv_init函數中創建不同的子設備節點

first_drv_exti函數中註銷不同的子設備節點

first_drv_open函數中通過MINOR(inode->i_rdev)來初始化不同的燈

first_drv_write函數中通過MINOR(file->f_dentry->d_inode->i_rdev)來控制不同的燈

如下圖,insmod後自動註冊3個設備節點

技術分享

測試程序如下:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
 
 /*
  *  ledtest <dev> <on|off>
  */

void print_usage(char *file)         //報錯打印幫助
{
    printf("Usage:\n");
    printf("%s <dev> <on|off>\n",file);
    printf("eg. \n");
    printf("%s /dev/leds on\n", file);
    printf("%s /dev/leds off\n", file);
    printf("%s /dev/led1 on\n", file);
    printf("%s /dev/led1 off\n", file);
} 

int main(int argc, char **argv)
{
    int fd;
    char* filename;
    char val;
    
if (argc != 3) { print_usage(argv[0]); return 0; } filename = argv[1]; fd = open(filename, O_RDWR); if (fd < 0) { printf("error, can‘t open %s\n", filename); return 0; } if (!strcmp("on", argv[2])) { // 亮燈 val = 0; write(fd, &val, 1); } else if (!strcmp("off", argv[2])) { // 滅燈 val = 1; write(fd, &val, 1); } else //數據輸入錯誤,打印幫助提示 { print_usage(argv[0]); return 0; } return 0; }

驅動程序如下:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/irq.h>
#include <asm/arch/regs-gpio.h> #include <asm/hardware.h> #include <asm/uaccess.h> #include <asm/io.h> static struct class *firstdrv_class; //創建一個class類 static struct class_device *firstdrv_class_devs[4]; //創建類的設備,led,led1,led2,led3 volatile unsigned long *GPFcon=NULL; volatile unsigned long *GPFdat=NULL; /*1寫出驅動程序first_drv_open first_drv_write */ static int first_drv_open(struct inode *inode, struct file *file) { int minor=MINOR(inode->i_rdev); printk("first_drv_open\n"); //打印,在內核中打印只能用printk() GPFcon = ioremap(0x56000050, 16); //ioremap:物理地址映射,返回虛擬地址 GPFdat=GPFcon+1; //long:32位,所以GPFdat=0x56000050+(32/8)
switch(minor) { case 0: //進入led設備,控制所有led *GPFcon&=~ ((0X3<<8)| (0X3<<10)| (0X3<<12)); *GPFcon|= ((0X01<<8)| (0X01<<10)| (0X01<<12)); break; case 1: //進入led1設備,控制 led1 *GPFcon&=~ ((0X3<<8) ); *GPFcon|= (0X1<<8) ; break; case 2: //進入led2設備,控制 led2 *GPFcon&=~ ((0X3<<10) ); *GPFcon|= (0X1<<10) ; break; case 3: //進入led3設備,控制 led3 *GPFcon&=~ ((0X3<<12) ); *GPFcon|= ((0X1<<12) ); break; } return 0; } /*參數filp為目標文件結構體指針,buffer為要寫入文件的信息緩沖區,count為要寫入信息的長度,ppos為當前的偏移位置,這個值通常是用來判斷寫文件是否越界*/ static ssize_t first_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos) { int val; int minor=MINOR(file->f_dentry->d_inode->i_rdev); copy_from_user(&val,buf,count); //通過用戶(應用層)拷貝數據 switch(minor) { case 0: //進入led設備,控制所有led printk("led0,%d\n",val); if(val) //開燈 {*GPFdat&=~ ((0X1<<4)| (0X1<<5)| (0X1<<6)); *GPFdat|= ((0X0<<4)| (0X0<<5)| (0X0<<6)); } else //關燈 {*GPFdat&=~ ((0X1<<4)| (0X1<<5)| (0X1<<6)); *GPFdat|= ((0X1<<4)| (0X1<<5)| (0X1<<6)); } break; case 1: //進入led1設備,控制 led1 printk("led1,%d\n",val); if(val) //開燈 {*GPFdat&=~ (0X1<<4); *GPFdat|= (0X0<<4); } else //關燈 { *GPFdat&=~ (0X1<<4); *GPFdat|= (0X1<<4); } break; case 2: //進入led2設備,控制 led2 printk("led2,%d\n",val); if(val) //開燈 {*GPFdat&=~ (0X1<<5); *GPFdat|= (0X0<<5); } else //關燈 {*GPFdat&=~ (0X1<<5); *GPFdat|= (0X1<<5); } break; case 3: //進入led3設備,控制 led3 printk("led3,%d\n",val); if(val) //開燈 {*GPFdat&=~ (0X1<<6); *GPFdat|= ( 0X0<<6); } else //關燈 {*GPFdat&=~ (0X1<<6); *GPFdat|= (0X1<<6); } break; } return 0; } /*2定義file_operations結構體來封裝驅動函數first_drv_open first_drv_write */ static struct file_operations first_drv_fops = { .owner = THIS_MODULE, //被使用時阻止模塊被卸載 .open = first_drv_open, .write = first_drv_write, }; int major; //定義一個全局變量,用來保存主設備號 int first_drv_init(void) { int i; /*3 register_chrdev註冊字符設備*/ /*如果設置major為0,表示由內核動態分配主設備號,函數的返回值是主設備號*/ major=register_chrdev (0, "first_drv", &first_drv_fops);
firstdrv_class
= class_create(THIS_MODULE,"firstdrv"); //創建類,它會在sys目錄下創建firstdrv這個類 firstdrv_class_devs[0]=class_device_create(firstdrv_class,NULL,MKDEV(major,0),NULL,"led"); //創建類設備,它會在firstdrv_class類下創建led設備,然後mdev通過這個自動創建/dev/xyz這個設備節點 for(i=1;i<4;i++) //創建led1 led2 led3 設備節點,控制led1 led2 led3 { firstdrv_class_devs[i]=class_device_create(firstdrv_class,NULL,MKDEV(major,i),NULL,"led%d",i); } return 0; } /*6 寫first_drv_exit出口函數*/ void first_drv_exit(void) { int i; unregister_chrdev (major, "first_drv"); //卸載驅動,只需要主設備號和設備名就行 class_destroy(firstdrv_class); //註銷類,與class_create對應 for(i=0;i<4;i++) //註銷類設備led,led1,led2,led3 class_device_unregister(firstdrv_class_devs[i]);
iounmap(GPFcon);
//註銷虛擬地址 } /*5 module_init修飾入口函數*/ module_init(first_drv_init); /*7 module_exit修飾出口函數*/ module_exit(first_drv_exit); MODULE_LICENSE("GPL v2"); //聲明許可證

3.修改第一個程序來點亮LED