先來一張架構圖
當執行 sudo insmod dht11km.ko 時.... 就會啟動 module driver init function
當執行 sduo rmmod dht11km.ko 時.... 就會啟動 module driver exit function
step 1: dht11_init_module
他一開始會先確定你傳進去的gpio_pin 的number 有沒有合法
// Possible valid GPIO pins
int valid_gpio_pins[] = { 0, 1, 4, 8, 7, 9, 10, 11, 14, 15, 17, 18, 21, 22, 23, 24, 25 };
他宣告了一個valid_gpio_pins陣列.... 然後去檢查有沒有符合的...
如果一個都沒有符合...就會
printk(KERN_ERR DHT11_DRIVER_NAME ": invalid GPIO pin specified!\n");
goto exit_rpi;
接下來就是將driver 註冊到 kernel 的 VFS層
result = register_chrdev(driverno, DHT11_DRIVER_NAME, &fops);
從參考1的網站可以看出有四種註冊的類型...
在本例子中用的是char type driver
int register_chrdev(unsigned int major, const char * name, struct file_operations *fops):註冊字元型驅動程式。
˙ int register_blkdev(unsigned int major, const char *name, struct file_operations *fops):註冊區塊型驅動程式。
˙ int usb_register(struct usb_driver *new_driver):註冊USB驅動程式。
˙ int pci_register_driver(struct pci_driver *):註冊PCI驅動程式。
接著就是呼叫 init_port();
request_mem_region(GPIO_BASE, SZ_4K, DHT11_DRIVER_NAME)
gpio = ioremap_nocache(GPIO_BASE, SZ_4K)
這是原本的內容 .... 他去呼叫 request_mem_region 去註冊一塊記憶體
然後用ioremap_nocache() 去map ....最後得到gpio 這個virtual address
不過由於我在Raspiban 上面實作....發現這個空間已經被註冊了
所以只好改source code
把這一行 request_mem_region(GPIO_BASE, SZ_4K, DHT11_DRIVER_NAME) Mark
step 2: dht11_exit_module
source code 如下:if(gpio != NULL) {
iounmap(gpio);
release_mem_region(GPIO_BASE, SZ_4K);
printk(DHT11_DRIVER_NAME ": cleaned up resources\n");
}
如果gpio的數值不為零.... 就把 gpio 所使用的這塊記憶體 unmap掉
然後把記憶體釋放掉
接下來就是把註冊的driver 取消掉
unregister_chrdev(driverno, DHT11_DRIVER_NAME);
step 3 : protocol 詳解
從上面2圖來看.... 從算 bit 0 和 bit 1 都是需要兩個動作...
第一個動作是把voltage 拉 low 50us ...接下來再把 voltage 拉 high...
如果維持時間有 26-28 us ...就是代表value 0
如果維持時間有 70 us ...就是代表value 1
在DHT11 的整個傳送封包來看....總共有40個bit
8 bit 濕度整數 + 8 bit 濕度小數
8 bit 溫度整數 + 8 bit 溫度小事
8 bit CRC...
接下來看 raspberry 和 DHT11 的溝通protocol
Raspberry 這邊簡稱 Host... DHT11 這邊簡稱 Device
一開始 Host 先拉low ...維持 18ms .... 然後拉High 維持20-40 us.....
然後把 Raspberry 把gpio 設成 input mode...把控制權交給Device
接著device 先把電位拉low 維持80us ...再把 電位拉 high 維持 80us ..
這個動作是在告訴host ...我已經接收到命令....接下來就是 傳送40 各 bit....
step 4 : GPIO function
先來一張 soc chip 在gpio 有關的register table
以 GPFSEL0 來看 (offset 0x0).... 控制 gpio 0 到 9
他可以控制10 根GPIO.... bit[2:0] 控制 gpio0 .. bit[5:3] 控制 gpio1 ...以此類推
雖然有三個bit ...有8種可能...但這邊我們只會用到 input mode 或是 output mode
put '0' -> input mode
put '1' -> output mode
決定了 GPIO 的 input mode 或是 ouput mode 之後 ..... 以output mode為例子
從上圖可以得知....
GPSET0 (offset 0x1C).... GPSET1(offset 0x20)
GPSET0 決定了 gpio[31:0] 的控制.....
假設你要讓 gpio 10 的值為 '1' ....
你就需要 GPSET0[10] 設成 '1'.... 寫 value 0 是沒有效果的...
GPCLR0 (offset 0x28).... GPCLR1(offset 0x2C)
假設你要讓 gpio 10 的值為 '0' ....
你就需要 GPCLR0[10] 設成 '1'.... 寫 value 0 是沒有效果的...
接下來看當設成 input mode 時...
如果你要得到GPIO[n] 的數值..... 請 read GPLEV0 (0x38) 和 GPLEV1 (0x3C)
假如你要得到GPIO[10] 的數值....就是 GPLEV0[10] 的value
接下來看設定 rising edge GPREN(0x40)和 falling edge GPFEN(0x4C)
假設你要設定 GPIO 4 為 rising edge event trigger
GPREN[4] 為 '1'.... 如果要disable他.... GPREN[4] 為 '0'
假設你要設定 GPIO 4 為 falling edge event trigger
GPFEN[4] 為 '1'.... 如果要disable他.... GPREN[4] 為 '0'
接下來要把來看 GPIO Event status Register ...GPEDS0(0x40)
他會被硬體設成'1'如果符合下面兩種狀況...
第一種 : gpio edge 的行為被偵測到了並且符合你設定rising 或是 falling edge 的行為
第二種 : gpio level 的行為被偵測到了並且符合你設定 level的狀況....
假設你要清除GPIO 4 所引起的中斷結果..... GPESD0[4] = 1; 就這麼簡單
看完上述的data sheet之後...再回來看source code...你就會知道為什麼code要這樣寫了...
// set GPIO pin g as input
#define GPIO_DIR_INPUT(g) *(gpio+((g)/10)) &= ~(7<<(((g)%10)*3))
// set GPIO pin g as output
#define GPIO_DIR_OUTPUT(g) *(gpio+((g)/10)) |= (1<<(((g)%10)*3))
// get logical value from gpio pin g
#define GPIO_READ_PIN(g) (*(gpio+13) & (1<<(g))) && 1
// sets bits which are 1 ignores bits which are 0
#define GPIO_SET_PIN(g) *(gpio+7) = 1<<g;
// clears bits which are 1 ignores bits which are 0
#define GPIO_CLEAR_PIN(g) *(gpio+10) = 1<<g;
// Clear GPIO interrupt on the pin we use
#define GPIO_INT_CLEAR(g) *(gpio+16) = (*(gpio+16) | (1<<g));
// GPREN0 GPIO Pin Rising Edge Detect Enable/Disable
#define GPIO_INT_RISING(g,v) *(gpio+19) = v ? (*(gpio+19) | (1<<g)) : (*(gpio+19) ^ (1<<g))
// GPFEN0 GPIO Pin Falling Edge Detect Enable/Disable
#define GPIO_INT_FALLING(g,v) *(gpio+22) = v ? (*(gpio+22) | (1<<g)) : (*(gpio+22) ^ (1<<g))
GPIO_DIR_INPUT 是把指定的GPIO n 所對應到的 bit 清成 0
舉例來說.... 假設 我們用 gpio 4.... g 為 4
(g%10) *3 = 12 ... bit[14:12] 就是我們要設定的位置....
7 <<12 = 0x00 00 07 00 (16進位)
~(7<<12) = 0xff ff f8 ff ..剛好就是 bit[14:12] 為0....其他都為1
然後這邊清成0的方式是用 and 的方式.... 就可以把 bit[14:12] 清成 0
GPIO_DIR_OUTPUT(g) 也是類似的原理....這邊就不多說 ....
GPIO_SET_PIN(g) -> *(gpio+7) = 1<<g;
首先 offset = 7*4 = 28 -> 0x1C ....然後針對你要gpio 的number 去設定
GPIO_CLEAR_PIN(g) -> *(gpio+10) = 1<<g;
首先 offset = 10*4 = 40 -> 0x28 ....然後針對你要gpio 的number 去設定
GPIO_READ_PIN(g) -> (*(gpio+13) & (1<<(g))) && 1
首先 offset = 13*4 = 52 -> 0x34 ....把數值讀回來後... 用 mask 1<<g 去做and
把只有那個bit 的數值留下來...其他都變成0....然後再和 1做 && 的運算
如果不為零....就是1....如果為零...就是0.....
GPIO_INT_CLEAR(g) *(gpio+16) = (*(gpio+16) | (1<<g));
就是打開 Event status register .... 把你要的相對應的bit 設成1
GPIO_INT_RISING(g,v) *(gpio+19) = v ? (*(gpio+19) | (1<<g)) : (*(gpio+19) ^ (1<<g))
這個macro 是說.... 如果v 為 1...也就是要enable rising edge ....就是把相對應的bit 設成 '1'
如果v 為 0...也就是要disable rising edge ....就是把相對應的bit 設成 '0'
GPIO_INT_FALLING(g,v) *(gpio+22) = v ? (*(gpio+22) | (1<<g)) : (*(gpio+22) ^ (1<<g))
這個macro 是說.... 如果v 為 1...也就是要enable falling edge ....就是把相對應的bit 設成 '1'
如果v 為 0...也就是要disable falling edge ....就是把相對應的bit 設成 '0'
step 5 : read function 實作
先看 source code :
GPIO_DIR_OUTPUT(gpio_pin); // Set pin to output
GPIO_CLEAR_PIN(gpio_pin); // Set low
mdelay(20); // DHT11 needs min 18mS to signal a startup
GPIO_SET_PIN(gpio_pin); // Take pin high
udelay(40); // Stay high for a bit before swapping to read mode
GPIO_DIR_INPUT(gpio_pin); // Change to read
和上面的步驟一樣.....一開始先設成 output mode
然後設成low ...... 等待 18ms ... mdealy(20)
然後拉high...... 等待 40 us.... udelay(40)
然後設成 input mode...把控制權交給 device
接下來的動作為
do_gettimeofday(&lasttv);
setup_interrupts();
mdelay(10);
if((dht[0] + dht[1] + dht[2] + dht[3] == dht[4]) & (dht[4] > 0))
sprintf(result, "OK");
else
....
do_gettimeofday 為 linux 內建的function ...可以取得目前的時間資訊
setup_interrupts() 為自己設定的sub-function...接下來會詳細說明
mdelay(10) 這個是在等待device 傳資訊..... 由於剛剛已經設定了 interrupt ...
當gpio 產生變化時...就會啟動ISR 去處理....這邊就只是不處理了
接下來就是check CRC .... 因為總共會傳 40 bit 也就是 5 byte...
分別放到 dht[0].. dht[1].. dht[2]... dht[3]... dht[4]... 然後dht[4]也就是CRC值
看起來應該是全部加起來會等於CRC..... 所以就去check值有沒有對...
接下來解釋 setup_interrupts()
result = request_irq(INTERRUPT_GPIO0, (irq_handler_t) irq_handler, 0, DHT11_DRIVER_NAME, (void*) gpio);
// GPREN0 GPIO Pin Rising Edge Detect Enable
GPIO_INT_RISING(gpio_pin, 1);
// GPFEN0 GPIO Pin Falling Edge Detect Enable
GPIO_INT_FALLING(gpio_pin, 1);
// clear interrupt flag
GPIO_INT_CLEAR(gpio_pin);
spin_unlock_irqrestore(&lock, flags);
第一步是先註冊 irq....
request_irq(INTERRUPT_GPIO0, (irq_handler_t) irq_handler, 0, DHT11_DRIVER_NAME, (void*) gpio);
當irq來的時候.... 會啟動 irq_handler() 去執行....
接下來是設定 gpio_pin 的觸發行為為 Rising edge 和 falling edge....
然後清除 之前的中斷狀態....
irq_handler的內容如下:
// use the GPIO signal level
signal = GPIO_READ_PIN(gpio_pin);
/* reset interrupt */
GPIO_INT_CLEAR(gpio_pin);
do_gettimeofday(&tv);
// get time since last interrupt in microseconds
deltv = tv.tv_sec-lasttv.tv_sec;
data = (int) (deltv*1000000 + (tv.tv_usec - lasttv.tv_usec));
lasttv = tv; //Save last interrupt time
首先先去讀目前gpio的value...... 把value存放在 signal...
然後清除目前的中斷狀態......
接下來讀一下目前的時間....並且計算出和上次產生中斷的時間的差值....
並且把這個數值存放到 data 裡面....單位 us.....
接下來就是判斷....
if((signal == 1)&(data > 40))
{
started = 1;
return IRQ_HANDLED;
}
如果signal 為'1' ...代表這個中斷是rising edge.... 並且check拉low 的時間有大於 40 us
就直接返回...等待下一次的中段...
if((signal == 0)&(started==1))
{
if(data > 80)
return IRQ_HANDLED; //Start/spurious? signal
if(data < 15)
return IRQ_HANDLED; //Spurious signal?
if (data > 60)//55
dht[bytecount] = dht[bytecount] | (0x80 >> bitcount); //Add a 1 to the data byte
//Uncomment to log bits and durations - may affect performance and not be accurate!
//printk("B:%d, d:%d, dt:%d\n", bytecount, bitcount, data);
bitcount++;
if(bitcount == 8)
{
bitcount = 0;
bytecount++;
}
}
如果signal 為 0...代表目前是 falling edge...
if 時間大於80 us....可能是 開始或是結束的header...不是我們要的資料...就返回
else if 時間小於15 us. 可能是Spurious signal 不是我們要的資料...就返回
else if時間大於 60 us ....代表傳送的bit為 '1'..
.dht[bytecount] = dht[bytecount] | (0x80 >> bitcount);
else 代表傳送的bit 為 '0'
bit_count ++....確認一下byte_count.....
Reference 1: http://www.jollen.org/blog/2006/05/linux_5_virtual_device_driver.html
Reference 2 : http://www.slideshare.net/raspberrypi-tw/write-adevicedriveronraspberrypihowto
Reference 3 : http://silverfoxkkk.pixnet.net/blog/post/44691351-lddp%3A%E4%B9%9D%E3%80%81%E8%A8%88%E6%99%82%E5%99%A8