我們之前的驅動程序已經把框架寫好,只是打印一些語句,沒有做其他的事,現在我們讓他點亮led燈
驅動的硬件操作分爲3步:
-
看原理圖
-
看芯片手冊(這裏是S3C2440)
-
寫硬件相關代碼
linux驅動的硬件相關代碼和單片機的有什麼不同呢
單片機的是直接操作物理地址
linux的是操作虛擬地址(由ioremap函數映射,物理地址——>虛擬地址)
1.查看原理圖
(1)通過查看原理圖,我們知道要led的引腳爲GPF4,5,6
2.查看芯片手冊
搜索GPFCON,知道要操作GPFCON和GPFDAT寄存器,他們的物理地址如下:
點亮led需要配置GPFCON寄存器,輸出模式
設置GPFDATE寄存器,來輸出高低電平
我們把配置GPFCON放在open函數,把設置GPFDATE放在write函數
(1)配置GPF4,5,6爲輸出,操作GPFCON寄存器
(2)設置GPFDATE寄存器,來輸出高低電平
3.設置GPFCON寄存器
(1)由芯片手冊知道GPFCON寄存器的物理地址爲0x56000050,linux操作是使用虛擬地址,所以我們要使用ioremap把物理地址映射爲虛擬地址,在入口函數裏面配置,每次打開就自動配置好
搜索ioremap的使用方法,參數爲起始地址和長度(映射空間爲從起始地址+長度這麼一段空間,這裏由於有4個寄存器,用16字節),由於32位的指針都是4字節,所以gpfdat= gpfcon + 1,完善:
在出口地址設置取消映射:
以後操作寄存器的時候就用指針gpfcon和gpfdat操作引腳
(2)操作
A &= ~B爲位清零
A |= B 爲位置1
先爲清零在置1設置輸出模式,由於是2位,用0x03(兩個1)清零,01爲輸出,用0x01置1.
4.設置GPFDAT寄存器
在write函數裏操作
我們要點亮關閉,需要在應用層輸入參數on/off,在應用層的參數怎麼傳到內核層的驅動函數,用copy_from_user()函數
然後設置val是1或者0開燈關燈
5.編寫測試程序
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
/*
*myled_test on
*myled_test off
*/
int main(int argc, char **argv)
{
int fd;
int val = 1;
fd = open("/dev/myled", O_RDWR);
if (fd < 0)
{
printf("can't open!\n");
}
if (argc != 2)
{
printf("Usage :\n");
printf("%s <on|off>\n", argv[0]);
return 0;
}
if (strcmp(argv[1], "on") == 0)
{
val = 1;
}
else
{
val = 0;
}
write(fd, &val, 4);
return 0;
}
驅動代碼爲:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
volatile unsigned long *gpfcon = NULL;
volatile unsigned long *gpfdat = NULL;
static struct class *firstdrv_led_class;
static struct class_device *firstdrv_led_class_dev;
static int firstdrv_led_open(struct inode *inode, struct file *file)
{
/*配置GPF 4 5 6 爲輸出*/
*gpfcon &= ~((0x03)<<(4*2)|(0x03)<<(5*2)|(0x03)<<(6*2));
*gpfcon |= ((0x01)<<(4*2)|(0x01)<<(5*2)|(0x01)<<(6*2));
return 0;
}
static ssize_t firstdrv_led_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
int val;
copy_from_user(&val,buf,count); //由應用層的write(fd, &val, 4)函數的val參數傳到內核層,讓我們可以使用應用層的val值
//把buf=&val(write(fd, &val, 4)函數中的&val)傳給copy_from_user(&val,buf,count)中的val
//對應的內核層到應用層是用copy_to_user()
if(val == 1)
{
//點燈,輸出低電平
*gpfdat &= ~((1<<4) | (1<<5) | (1<<6));
}
else
{
//滅燈,高電平
*gpfdat |=(1<<4)|(1<<5)|(1<<6);
}
return 0;
}
/* 這個結構是字符設備驅動程序的核心
* 當應用程序操作設備文件時所調用的open、read、write等函數,
* 最終會調用這個結構中指定的對應函數
*/
static struct file_operations firstdrv_led_fops = {
.owner = THIS_MODULE, /* 這是一個宏,推向編譯模塊時自動創建的__this_module變量 */
.open = firstdrv_led_open,
.write = firstdrv_led_write,
};
int major;
int firstdrv_led_init(void)
{
major =register_chrdev(0,"myled",&firstdrv_led_fops);//註冊firstdrv,告訴內核,註冊設備時給設備號寫0,則內核會自己主動分配一個主設備號返回。
firstdrv_led_class = class_create(THIS_MODULE,"firstdrv_led");
firstdrv_led_class_dev = class_device_create(firstdrv_led_class,NULL,MKDEV(major,0),NULL,"myled");
gpfcon = (volatile unsigned long *)ioremap(0x56000050,16);
gpfdat= gpfcon + 1;
return 0;
}
void firstdrv_led_exit(void)
{
unregister_chrdev(major,"firstdrv_led");//卸載
class_device_unregister(firstdrv_led_class_dev);
class_destroy(firstdrv_led_class);
iounmap(gpfcon);
}
module_init(firstdrv_led_init);
module_exit(firstdrv_led_exit);
MODULE_LICENSE("GPL");
6.測試
結果燈成功亮滅
7.總結
(1)寫驅動框架(open,write等,file_operations結構體,註冊,自動創建設備節點)
(2)硬件操作(看原理圖,手冊,ioremap映射)
8.拓展(次設備號的應用)
我們的實驗結果是傳進1,就點亮三盞燈,0就滅燈,那有沒有方法單獨點亮某盞燈,可以使用不同的val,但是太麻煩,這裏我們使用次設備號對應不同的燈來操作,之前我們生成的是: