驅(qū)動的兩大利器:電路圖(通過電路圖去尋找寄存器)和芯片手冊
樹莓派使用的是BCM2835CPU(博通),芯片手冊做到哪一章就看哪一章。芯片提供了54個IO口,對應了樹莓派的 BCM.


一、樹莓派GPIO口介紹
根據(jù)手冊知gpio在第6章,我們來看第89頁

GPFSEL0是pin0 ~ pin9的配置寄存器,GPFSEL1是pin10 ~ pin19的配置寄存器,以此類推,GPFSEL5就是pin50~pin53的配置寄存器。
GPFSEL0 |
GPIO Function Select 0:功能選擇輸入或輸出 |
GPSET0 |
GPIO Pin Output Set 0:輸出0 |
GPSET1 |
GPIO Pin Output Set 1:輸出1 |
GPCLR0 |
GPIO Pin Output Clear 0:清零 |
下圖給出第九個引腳的功能選擇示例,對寄存器的29-27進行配置,進而設置相應的功能。 根據(jù)圖片下方的register 0表示0~9使用的是register 0這個寄存器。

輸出集寄存器用于設置GPIO管腳。SET{n}字段定義,分別對GPIO引腳進行設置,將“0”寫入字段沒有作用。如果GPIO管腳為在輸入(默認情況下)中使用,那么SET{n}字段中的值將被忽略。然而,如果引腳隨后被定義為輸出,那么位將被設置根據(jù)上次的設置/清除操作。分離集和明確功能取消對讀-修改-寫操作的需要。GPSETn寄存器為了使IO口設置為1,set4位設置第四個引腳,也就是寄存器的第四位

輸出清除寄存器用于清除GPIO管腳。CLR{n}字段定義要清除各自的GPIO引腳,向字段寫入“0”沒有作用。如果的在輸入(默認),然后在CLR{n}字段的值是忽略了。然而,如果引腳隨后被定義為輸出,那么位將被定義為輸出根據(jù)上次的設置/清除操作進行設置。分隔集與清函數(shù)消除了讀-修改-寫操作的需要。GPCLRn是清零功能寄存器

二、代碼的編寫
編寫驅(qū)動程序時,首先要知道它的地址,IO口空間的起始地址是0x3f00 0000(文檔的起始地址是錯誤的),加上GPIO的偏移量0x200 0000,所以GPIO的物理地址應該是0x3f20 0000開始的,然后在這個基礎上進行Linux系統(tǒng)的MMU內(nèi)存虛擬化管理,映射到虛擬地址上。

上圖尾部的偏移量是正確的,根據(jù)gpio的物理地址0x3f200 0000得到
GPFSEL0 0x3f20 0000 //IO口的初始的物理地址,而并不是手冊里面的那個總線地址
GPSET0 0x3f20 001c //地址通過查找芯片手冊里面的對應的GPSET0 的總線地址的后兩位決定是1c
GPCLR0 0x3f20 0028 //地址是查找GPCLR0在芯片手冊里的總線地址確定的28,所以地址后兩位是28
1.首先在原來的驅(qū)動框架上添加寄存器的定義
volatile unsigned int* GPFSEL0 = NULL;
volatile unsigned int* GPSET0 = NULL;
volatile unsigned int* GPCLR0 = NULL;
volatile關鍵字的作用:確保指令不會因編譯器的優(yōu)化而省略,且要求每次直接讀值,在這里的意思就是確保地址不會被編譯器更換。
2.然后在pin4_drv_init這個函數(shù)里面添加寄存器地址的配置
GPFSEL0 = (volatile unsigned int *)ioremap(0x3f200000,4);
GPSET0 = (volatile unsigned int *)ioremap(0x3f20001C,4);
GPCLR0 = (volatile unsigned int *)ioremap(0x3f200028,4);
ioremap將物理地址轉(zhuǎn)換為虛擬地址
我們前面講到了在內(nèi)核里代碼和上層代碼訪問的是虛擬地址(VA),而現(xiàn)在設置的是物理地址,**所以必須把物理地址轉(zhuǎn)換成虛擬地址**
按位與或按位或
配置引腳4為輸出引腳,為了不影響其他引腳,需要使用與運算或運算。

根據(jù)圖片可知14-12bit需配置成001.
31 30 ······14 13 12 11 10 9 8 7 6 5 4 3 2 1
0 0 ······0 0 1 0 0 0 0 0 0 0 0 0 0 0
//配置pin4引腳為輸出引腳 bit 12-14 配置成001
*GPFSEL0 &= ~(0x6 <<12); // 把bit13 、bit14置為0
//0x6是110 <<12左移12位 ~取反 &按位與
*GPFSEL0 |= (0x1 <<12); //把12置為1 |按位或
4.讓引腳拉高
if(userCmd == 1)
{
printk("set 1\n");
*GPSET0 |= (0x1 << 4);
//寫1左移4位是讓寄存器 開啟置1 讓bit4為高電平
}
else if(userCmd == 0)
{
printk("set 0\n");
*GPCLR0 |= (0x1 << 4);
//寫1左移4位是讓清0寄存器 開啟置0 讓bit4為低電平
}
else
{
printk("nothing undo\n");
}
補充:ioremap用法
開始映射:void* ioremap(unsigned long phys_addr , unsigned long size , unsigned long flags)
//用map映射一個設備意味著使用戶空間的一段地址關聯(lián)到設備內(nèi)存上,這使得只要程序在分配的地址范圍內(nèi)進行讀取或?qū)懭?,實際上就是對設備的訪問。
phys_addr:要映射的起始的IO地址
size:要映射的空間的大小
flags:要映射的IO空間和權限有關的標志
第二個參數(shù)怎么定?
這個由你的硬件特性決定。
比如,你只是映射一個32位寄存器,那么長度為4就足夠了。
(這里樹莓派IO口功能設置寄存器、IO口設置寄存器都是32位寄存器,所以分配四個字節(jié)就夠了)
比如:GPFSEL0=(volatile unsigned int *)ioremap(0x3f200000,4);
GPSET0 =(volatile unsigned int *)ioremap(0x3f20001C,4);
GPCLR0 =(volatile unsigned int *)ioremap(0x3f200028,4);
這三行是設置寄存器的地址,volatile的作用是作為指令關鍵字
確保本條指令不會因編譯器的優(yōu)化而省略,且要求每次直接讀值
ioremap函數(shù)將物理地址轉(zhuǎn)換為虛擬地址,IO口寄存器映射成普通內(nèi)存單元進行訪問。
解除映射:void iounmap(void* addr)//取消ioremap所映射的IO地址
比如:
iounmap(GPFSEL0);
iounmap(GPSET0);
iounmap(GPCLR0); //卸載驅(qū)動時釋放地址映射
函數(shù)copy_from_user用法。
函數(shù)copy_from_user原型:
copy_from_user(void *to, const void __user *from, unsigned long n)
返回值:失敗返回沒有被拷貝成功的字節(jié)數(shù),成功返回0
參數(shù)詳解:
1. to 將數(shù)據(jù)拷貝到內(nèi)核的地址,即內(nèi)核空間的數(shù)據(jù)目標地址指針
2. from 需要拷貝數(shù)據(jù)的地址,即用戶空間的數(shù)據(jù)源地址指針
3. n 拷貝數(shù)據(jù)的長度(字節(jié))
也就是將@from地址中的數(shù)據(jù)拷貝到@to地址中去,拷貝長度是n
三、代碼整合
驅(qū)動代碼
#include <linux/fs.h> //file_operations聲明
#include <linux/module.h> //module_init module_exit聲明
#include <linux/init.h> //__init __exit 宏定義聲明
#include <linux/device.h> //class devise聲明
#include <linux/uaccess.h> //copy_from_user 的頭文件
#include <linux/types.h> //設備號 dev_t 類型聲明
#include <asm/io.h> //ioremap iounmap的頭文件
static struct class *pin4_class;
static struct device *pin4_class_dev;
static dev_t devno; //設備號
static int major =231; //主設備號
static int minor =0; //次設備號
static char *module_name="pin4"; //模塊名--這個模塊名到時候是在樹莓派的/dev底下顯示相關驅(qū)動模塊的名字
volatile unsigned int* GPFSEL0 = NULL;
volatile unsigned int* GPSET0 = NULL;
volatile unsigned int* GPCLR0 = NULL;
//volatile關鍵字的作用:確保指令不會因編譯器的優(yōu)化而省略,且要求每次直接讀值,在這里的意思就是確保地址不會被編譯器更換
//led_open函數(shù)
static int pin4_open(struct inode *inode,struct file *file)
{
printk("pin4_open\n"); //內(nèi)核的打印函數(shù)和printf類似
//由于pin4在 14-12位,所以將14-12位分別置為001即為輸出引腳,所以下面的那兩個步驟分別就是將14,13置為0,12置為1
*GPFSEL0 &= ~(0x6 << 12); //把13,14位 置為0
*GPFSEL0 |= (0x1 << 12); //把12位 置為1
return 0;
}
//led_write函數(shù)
static ssize_t pin4_write(struct file *file,const char __user *buf,size_t count, loff_t *ppos)
{
int userCmd;
int copy_cmd;
printk("pin4_write\\n");
//copy_from_user(void *to, const void __user *from, unsigned long n)
copy_cmd = copy_from_user(&userCmd,buf,count); //函數(shù)的返回值是,如果成功的話返回0,失敗的話就是返回用戶空間的字節(jié)數(shù)
if(copy_cmd != 0)
{
printk("fail to copy from user\n");
}
if(userCmd == 1)
{
printk("set 1\n");
*GPSET0 |= (0x1 << 4); //這里的1左移4位的目的就是促使寄存器將電平拉高,即變?yōu)镠IGH
}
else if(userCmd == 0)
{
printk("set 0\n");
*GPCLR0 |= (0x1 << 4); //這里的1左移4位也是一樣只是為了讓寄存器將電平拉低,即變?yōu)長OW
}
else
{
printk("nothing undo\n");
}
return 0;
}
static ssize_t pin4_read(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
printk("pin4_read\n");
return 0;
}
static struct file_operations pin4_fops = {
.owner = THIS_MODULE,
.open = pin4_open,
.write = pin4_write,
.read = pin4_read,
};
int __init pin4_drv_init(void) //設備驅(qū)動初始化函數(shù)(真實的驅(qū)動入口)
{
int ret;
devno = MKDEV(major,minor); //創(chuàng)建設備號
ret = register_chrdev(major, module_name,&pin4_fops); //注冊驅(qū)動 告訴內(nèi)核,把這個驅(qū)動加入到內(nèi)核驅(qū)動的鏈表中
pin4_class=class_create(THIS_MODULE,"myfirstdemo"); //這個是讓代碼在/dev目錄底下自動生成設備,自己手動生成也是可以的
pin4_class_dev =device_create(pin4_class,NULL,devno,NULL,module_name); //創(chuàng)建設備文件
//由于以下的地址全是物理地址,所以我們要將物理地址轉(zhuǎn)換成虛擬地址
GPFSEL0 = (volatile unsigned int *)ioremap(0x3f200000,4); //由于寄存器是32位的,所以是映射4個字節(jié),一個字節(jié)為8位
GPSET0 = (volatile unsigned int *)ioremap(0x3f20001c,4);
GPCLR0 = (volatile unsigned int *)ioremap(0x3f200028,4);
return 0;
}
void __exit pin4_drv_exit(void) //卸載驅(qū)動,即將驅(qū)動從驅(qū)動鏈表中刪除掉
{
iounmap(GPFSEL0);
iounmap(GPSET0);
iounmap(GPCLR0);
device_destroy(pin4_class,devno);
class_destroy(pin4_class);
unregister_chrdev(major, module_name); //卸載驅(qū)動
}
module_init(pin4_drv_init); //真正的入口
module_exit(pin4_drv_exit); //卸載驅(qū)動
MODULE_LICENSE("GPL v2");
上層代碼
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
int fd;
int userCmd;
fd = open("/dev/pin4",O_RDWR);
if(fd < 0)
{
printf("fail to open the pin4\n");
perror("the reason:");
}
else
{
printf("success to open the pin4\n");
}
printf("please Input 1-HIGH,0-LOW \n");
scanf("%d",&userCmd);
write(fd,&userCmd,4); //這里userCmd是一個整型數(shù),所以寫的是4個字節(jié)
return 0;
}
如何編譯驅(qū)動代碼并在樹莓派運行
樹莓派初始引腳

運行代碼后




學習筆記,僅供參考文章來源:http://www.zghlxwxcb.cn/news/detail-799836.html
優(yōu)秀博客文章來源地址http://www.zghlxwxcb.cn/news/detail-799836.html
到了這里,關于樹莓派BCM2835芯片手冊導讀以及IO口驅(qū)動代碼編寫的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!