最近想要做一個基于嵌入式Linux+Qt驅動dht11溫濕度傳感器的實驗。 想要實現的功能是通過野火的imx6ull開發板控制dht11傳感器,然后使用Qt做一個上位機,在上位機上面把數據顯示出來。
這里把我在做的過程中遇到的一些問題先記錄一下,免得日后忘記。
在網上關于這方面的資料不多,大多數都是基于stm32來控制的,所以在做的過程中遇到一些問題解決起來也比較麻煩。
下面簡述一下我做的過程及遇到的問題
首先查看原理圖看使用到了哪個管腳,然后在設備樹里添加相應的節點。 這里用到了gpio子系統和pinctrl子系統。
接著參考網上的相關代碼,進行了改寫,因為這個傳感器的時序也比較簡單,所以有關時序的部分基本上可以不用改。
遇到的第一個問題 :寫好驅動后,在應用程序中使用read函數來讀取設備文件,如果只讀取一次,可以得到結果,但是如果使用while(1)來嘗試反復讀取,就會失敗。
按照手冊來說,只要兩次讀取間隔超過1秒就行了,但是我使用while(1)即使休眠sleep(3)之類的依然會在第二次讀取的失敗,而且整個函數會卡死在讀取這里,這個進程怎么也殺不死,kill -9殺不死,kill -15 也殺不死。 這里很快把問題定位在了read函數。
后面,我在代碼中做了如下修改:本來在驅動程序里面有使用while函數來等待管腳電平的跳變,我認為這樣是不合理的,因為沒有超時處理,容易卡死,所以我加了一個計數,當超過一定計數值時就跳出while循環。 后來這個問題就解決了。 雖然我是不確定一開始是不是因為這個原因,因為中間過了挺久的時間,我不確定有沒有別的因素存在 ,總之后來就不會卡死了,可以使用while循環來反復讀取。
遇到的第二個問題 :在解決了上面的問題之后,insmod安裝驅動,可以工作,然后rmmod卸載驅動,再次insmod安裝驅動就會發現安裝不上去。
使用dmesg命令查看內核打印的信息,比較容易猜到應該是卸載驅動的時候沒有卸載干凈,然后仔細看了一下驅動,在結合網上查找資料,發現我的驅動里沒有寫remove函數。 所以我添加了remove函數,在remove函數里注銷掉那些東西。 而且要注意注銷的順序,和注冊是相反的,比如在驅動中最先是申請設備號,在注銷的時候就是最后注銷它,否則會出現很多錯誤,包括段錯誤 。
遇到的第三個問題 :在解決了第二個問題之后,已經可以反復卸載和安裝驅動了,但是發現一個問題,就是在第二次安裝的時候,總是會出現gpio_request失敗,按道理講我已經在remove函數里使用gpio_free釋放掉了,不應該會失敗才對,后來發現是在gpio_request的時候還沒拿到引腳號, 全局變量沒有初始化默認是0,所以request的是0,后來通過一個函數(忘記叫什么了,總之是gpio子系統的那些函數)從設備樹中拿到引腳號,這個引腳號是2,所以后面free的是2,也就是說request和free的不是同一個引腳,當然會出錯了。
這屬于粗心的錯,把這個問題解決了之后,這個驅動總算可以正常工作了,也完全可以反復卸載和安裝。
遇到的第四個問題 :在第一個問題里提到我在while里加了超時處理,防止一直死等卡死。 最開始我是這樣寫的
while(gpio_get_value(gpio)==0 &&cnt<6)< span="">
{
cnt++;
udelay(10);
}
這里通過cnt來防止while死掉,也就是說最多等待60微秒就退出循環。 但是直覺告訴我這樣不好 ,因為中間延時10個微秒太長了,導致響應性不好。 所以我改成了這樣:
while(gpio_get_value(gpio)==0 &&cnt<60)< span="">
{
cnt++;
udelay(1);
}
這樣的實時響應性好多了,測出的數據也更準確了。
到這里為止,驅動就基本沒有問題了,使用應用程序來讀取設備文件,也基本沒問題,就是有時數據校驗會失敗,但是測出的數據基本可以,而且是有變化的,說明還是比較可靠的。
接下來是把在Qt里把數據讀出來并且顯示, 下面說一下調試Qt遇到的問題 。
在寫完驅動之后,很自然會寫一個.c的測試程序,用來驗證驅動是否能正常工作,很幸運,一下子就成功了,于是我認為在Qt中也是類似,直接用Qt里的read相關的函數去讀取設備文件就好了,但是沒想到在這個環節卡了我最久
起初,我使用Qfile 里的readAll方法去讀,發現控制臺會刷屏(刷屏就是驅動中的read一直被調用而打印出的信息刷屏),一讀就停不下來,而且后面的程序也執行不了,也就是說函數沒有返回。
我不太清楚是什么原因,只能換一個函數,接著我嘗試了readLine方法,一樣刷屏,接著嘗試read方法,這個方法和C語言的read類似,參數里要填讀幾個字節,這和前面兩個不太一樣,所以我想,這回應該不會刷屏了吧。
結果確實沒有刷屏,但是讀取的數據是錯的,體現出來的就是從機無響應(這時我還沒有注意這個問題)。
雖然說數據是錯的,但是好歹沒有刷屏了,只要再想一想為什么會讀出錯的數據就行了。
我想到Qt里還有一種讀文件的方式,就是使用數據流Datastream,但是效果和上面的read一樣。
接著我開始思考刷屏的原因,百度了一下,有人說要在末尾加一個"\\0",嘗試,未果。
接著,我在一些技術交流群尋求幫助,因為此刻我的問題確實很奇怪,在自己寫的.c測試程序里,調用read讀設備文件是完全沒有問題的,現在唯一的區別就是在Qt中讀,驅動又不變,為什么讀出來的是錯的呢? 我懷疑是Qt的read對數據的解析可能和C語言里不太一樣,因為此刻是有數據的, 會不會是因為字節對齊之類的原因導致解析數據不對呢 ? 群里大佬建議先排查一下源數據對不對。
于是我拿出了我許久沒用過的邏輯分析儀來分析波形,我先觀察了我的.c測試程序的波形,和手冊描述的基本一致。 接著觀察Qt里read時的波形,一觀察發現根本沒有波形,正常情況應該是主機先拉低18ms,再拉高,等待從機應答。 而我觀察到的波形是主機拉低了30多ms才拉高,再看一下終端打印的數據, 發現驅動里的read被調用了兩次 。
這時,我已經猜到原因了,**之所以數據不對,是因為驅動里的read被連續調用了兩次,導致時序根本就不對,從機沒有應答。 **
再觀察之前使用readAll函數來讀取,雖然會刷屏,但是偶爾能捕捉到有效的波形。 這已經很能說明問題了,就是要解決驅動里的read為什么會被調用多次這個問題,正常應該是應用層調用一次read,驅動里的read就被調用一次。
關于這個問題,這篇文章講的不錯,使用cat讀取和echo寫內核文件節點的一些問題
這篇文章對我還是有很大的啟發。 總之就是驅動中read 的返回值會影響它是否被多次調用。
先來看一下驅動中read函數的參數和返回值
ssize_t dht11_chr_dev_read(struct file *filp, char __user *buf, size_t count, loff_t *fops)
我經過很多實驗,發現以下規律:
對于Qt中的readAll、readLine函數,不管驅動返回什么,readAll都會刷屏,readLine會調用驅動多次。
對于Qt中的read函數,如果驅動返回的是count,將不會刷屏,否則,也會刷屏。 (這一點確實很奇怪)
更奇怪的是同樣的實驗條件,在多次實驗中甚至可能得到不同的結果,但是上面這幾點結論是反復實驗得到的結論。
最后,我發現可以在Qt中使用C和C++混合編程,方法就是使用
extern "C"{
#include //這里寫用到的C頭文件
}
然后在用到的C語言的函數前加兩個冒號,比如
::read(fd,buf,sizeof(buf));
這樣就可以直接調用C語言代碼了,而且發現效果還不錯,比Qt中的read系列函數穩定。 (實驗次數有限,從我觀察到的結果來看是這樣)。
所以,最終的解決方法就是:
方法一 :使用Qfile 的read函數,使用方法和C語言類似,可以正確讀出數據,但是要注意,如果使用這個函數,驅動中的read要返回參數列表中的count,否則會刷屏。
方法二 :直接使用混合編程的方式,調用C語言中的read ,這樣測出的效果是最好的,而且不必要求驅動中的read 返回count,直接返回實際讀取的字節即可,也就是copy_to_user的字節數。
驅動代碼參考了Linux下DHT11驅動編程,以及測試程序
在此基礎上修改得到
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
/*------------------字符設備內容----------------------*/
#define DEV_NAME "dht11"
#define DEV_CNT (1)
typedef struct
{
uint8_t humi_int; //濕度的整數部分
uint8_t humi_deci; //濕度的小數部分
uint8_t temp_int; //溫度的整數部分
uint8_t temp_deci; //溫度的小數部分
uint8_t check_sum; //校驗和
} DHT11_Data_TypeDef;
//定義字符設備的設備號
static dev_t dht11_devno;
//定義字符設備結構體chr_dev
static struct cdev dht11_chr_dev;
struct class *class_dht11; //保存創建的類
struct device *device; // 保存創建的設備
struct device_node *dht11_device_node; //dht11的設備樹節點
int dht11_data_pin; // 保存獲取得到的dht11引腳編號
DHT11_Data_TypeDef DHT11_Data;
//從DHT11讀取1byte數據,MSB先行
uint8_t DHT11_ReadByte(void)
{
uint8_t i, temp=0;
int cnt=0;
for(i=0;i<8;i++)
{
/*每bit以50us低電平標置開始,輪詢直到從機發出 的50us 低電平 結束*/
while(gpio_get_value(dht11_data_pin) == 0 && cnt<60)
{
cnt++;
udelay(1);
}
cnt =0;
/*DHT11 以26~28us的高電平表示“0”,以70us高電平表示“1”,
*通過檢測 x us后的電平即可區別這兩個狀 ,x 即下面的延時
*/
udelay(40); //延時x us 這個延時需要大于數據0持續的時間即可
if(gpio_get_value(dht11_data_pin))/* x us后仍為高電平表示數據“1” */
{
/* 等待數據1的高電平結束 */
while(gpio_get_value(dht11_data_pin) && cnt<50)
{
cnt++;
udelay(1);
}
temp|=(uint8_t)(0x01<<(7-i)); //把第7-i位置1,MSB先行
}
else // x us后為低電平表示數據“0”
{
temp&=(uint8_t)~(0x01<<(7-i)); //把第7-i位置0,MSB先行
}
}
return temp;
}
/**
* 一次完整的數據傳輸為40bit,高位先出
* 8bit 濕度整數 + 8bit 濕度小數 + 8bit 溫度整數 + 8bit 溫度小數 + 8bit 校驗和的末8位
*/
uint8_t DHT11_Read_TempAndHumidity(DHT11_Data_TypeDef *DHT11_Data)
{
// int ret;
int cnt=0;
printk(KERN_ERR"DHT11_Read_TempAndHumidity 被調用\\n");
/*主機拉低*/
gpio_direction_output(dht11_data_pin, 0);
/*延時18ms,(>=18ms)*/
mdelay(18);
/*總線拉高 主機延時30us*/
gpio_direction_output(dht11_data_pin, 1);
udelay(30); //延時30us,(20~40us)
/*主機設為輸入 判斷從機響應信號*/
gpio_direction_input(dht11_data_pin);
/*判斷從機是否有低電平響應信號 如不響應則跳出,響應則向下運行*/
if(gpio_get_value(dht11_data_pin) == 0)
{
/*輪詢直到從機發出 的80us 低電平 響應信號結束*/
while(gpio_get_value(dht11_data_pin) == 0 && cnt<100)
{
cnt++;
udelay(1);
}
cnt = 0;
/*輪詢直到從機發出的 80us 高電平 標置信號結束*/
while(gpio_get_value(dht11_data_pin) && cnt<100)
{
cnt++;
udelay(1);
}
/*開始接收數據*/
DHT11_Data->humi_int= DHT11_ReadByte();
DHT11_Data->humi_deci= DHT11_ReadByte();
DHT11_Data->temp_int= DHT11_ReadByte();
DHT11_Data->temp_deci= DHT11_ReadByte();
DHT11_Data->check_sum= DHT11_ReadByte();
/*讀取結束,引腳改為輸出模式,主機拉高*/
gpio_direction_output(dht11_data_pin, 1);
printk("humi: %d.%d, temp: %d.%d,check:%d\\n",DHT11_Data->humi_int,\\
DHT11_Data->humi_deci,DHT11_Data->temp_int,DHT11_Data->temp_deci,DHT11_Data->check_sum);
/*檢查讀取的數據是否正確*/
//DHT11_Data->check_sum的正確的結果是溫濕度總和的末8位,結構體也有定義check_sum為uint8_t類型
if(DHT11_Data->check_sum == DHT11_Data->humi_int + DHT11_Data->humi_deci + DHT11_Data->temp_int+ DHT11_Data->temp_deci)
return 0;
else {
printk(KERN_ERR " ERROR 數據校驗失敗");
return -1;
}
}
else
{
printk(KERN_ERR "ERROR 從機無響應");
return -1;
}
}
/*字符設備操作函數集,open函數*/
static int dht11_chr_dev_open(struct inode *inode, struct file *filp)
{
printk("\\n open form driver \\n");
return 0;
}
/*字符設備操作函數集,write函數*/
static ssize_t dht11_chr_dev_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
unsigned char write_data; //用于保存接收到的數據
int error = copy_from_user(&write_data, buf, cnt);
if(error < 0) {
return -1;
}
return 0;
}
ssize_t dht11_chr_dev_read(struct file *filp, char __user *buf, size_t count, loff_t *fops)
{
int size=sizeof(DHT11_Data_TypeDef);
printk(KERN_ERR " count: %d, fops: %lld\\n", count, *fops);
printk(KERN_ERR "--------%s---------\\n",__func__);
/*調用DHT11_Read_TempAndHumidity讀取溫濕度,若成功則輸出該信息*/
if( DHT11_Read_TempAndHumidity ( & DHT11_Data ) != 0)
{
printk(KERN_ERR "Read DHT11 ERROR!\\r\\n");
}
else
{
if(copy_to_user(buf, &DHT11_Data, size)!=0)
{
printk(KERN_ERR " 拷貝失敗\\n");
// return 0;
}
else
printk(KERN_ERR " 拷貝成功\\n");
}
// ret= simple_read_from_buffer(buf, count, fops, &DHT11_Data, sizeof(DHT11_Data_TypeDef));
// *fops=0;
return count;
// return size;
}
/*字符設備操作函數集*/
static struct file_operations dht11_chr_dev_fops =
{
.owner = THIS_MODULE,
.open = dht11_chr_dev_open,
.write = dht11_chr_dev_write,
.read = dht11_chr_dev_read,
};
/*----------------平臺驅動函數集-----------------*/
static int dht11_probe(struct platform_device *pdv)
{
int ret = 0; //用于保存申請設備號的結果
printk(KERN_EMERG "\\t match successed \\n");
/*獲取dht11的設備樹節點*/
dht11_device_node = of_find_node_by_path("/dht11");
if(dht11_device_node == NULL)
{
printk(KERN_EMERG "\\t get dht11 failed! \\n");
}
dht11_data_pin = of_get_named_gpio(dht11_device_node, "dht11_data_pin", 0);
printk("dht11_data_pin = %d\\n ", dht11_data_pin);
ret=gpio_request(dht11_data_pin, "DQ_OUT");
if(ret==0)
{
printk(KERN_ERR "gpio request success\\n");
}
else
{
printk(KERN_ERR "gpio request failed \\n");
}
gpio_direction_output(dht11_data_pin, 1);
/*---------------------注冊 字符設備部分-----------------*/
//第一步
//采用動態分配的方式,獲取設備編號,次設備號為0,
//設備名稱為rgb-leds,可通過命令cat /proc/devices查看
//DEV_CNT為1,當前只申請一個設備編號
ret = alloc_chrdev_region(&dht11_devno, 0, DEV_CNT, DEV_NAME);
if(ret < 0){
printk("fail to alloc dht11_devno\\n");
goto alloc_err;
}
//第二步
//關聯字符設備結構體cdev與文件操作結構體file_operations
dht11_chr_dev.owner = THIS_MODULE;
cdev_init(&dht11_chr_dev, &dht11_chr_dev_fops);
//第三步
//添加設備至cdev_map散列表中
ret = cdev_add(&dht11_chr_dev, dht11_devno, DEV_CNT);
if(ret < 0)
{
printk(KERN_ERR"fail to add cdev\\n");
goto add_err;
}
//第四步
/*創建類 */
class_dht11 = class_create(THIS_MODULE, DEV_NAME);
if(class_dht11==NULL)
{
printk(KERN_ERR"class creat failed\\n");
goto add_class;
}
/*創建設備*/
device = device_create(class_dht11, NULL, dht11_devno, NULL, DEV_NAME);
if(device==NULL)
{
printk(KERN_ERR"device creat failed\\n");
goto add_device;
}
return 0;
// device_destroy(class_dht11,dht11_devno);
add_device:
class_destroy(class_dht11);
printk(KERN_EMERG "\\t 刪除類成功 \\n");
add_class:
cdev_del(&dht11_chr_dev);
printk(KERN_EMERG "\\t 刪除設備成功 \\n");
add_err:
//添加設備失敗時,需要注銷設備號
unregister_chrdev_region(dht11_devno, DEV_CNT);
printk(KERN_EMERG"\\n 注銷設備號成功! \\n");
alloc_err:
return -1;
}
int dht11_remove(struct platform_device *dht11_dev)
{
printk(KERN_EMERG"開始釋放資源");
gpio_free(dht11_data_pin);
device_destroy(class_dht11,dht11_devno);
class_destroy(class_dht11);
cdev_del(&dht11_chr_dev);
unregister_chrdev_region(dht11_devno, DEV_CNT);
printk(KERN_EMERG"釋放資源完畢");
return 0;
}
static const struct of_device_id dht11[] = {
{ .compatible = "dht11"},
{ /* sentinel */ }
};
/*定義平臺設備結構體*/
struct platform_driver dht11_platform_driver = {
.probe = dht11_probe,
.remove = dht11_remove,
.driver = {
.name = "dht11-platform",
.owner = THIS_MODULE,
.of_match_table = dht11,
}
};
/*
*驅動初始化函數
*/
static int __init dht11_platform_driver_init(void)
{
int DriverState;
DriverState = platform_driver_register(&dht11_platform_driver);
printk(KERN_EMERG "\\tDriverState is %d\\n",DriverState);
return 0;
}
/*
*驅動注銷函數
*/
static void __exit led_platform_driver_exit(void)
{
printk(KERN_EMERG "dht11 module exit!\\n");
platform_driver_unregister(&dht11_platform_driver);
}
module_init(dht11_platform_driver_init);
module_exit(led_platform_driver_exit);
MODULE_LICENSE("GPL");
-
嵌入式
+關注
關注
5069文章
19023瀏覽量
303454 -
Linux
+關注
關注
87文章
11232瀏覽量
208941 -
STM32
+關注
關注
2266文章
10875瀏覽量
354898 -
溫濕度傳感器
+關注
關注
5文章
574瀏覽量
35667 -
Qt
+關注
關注
1文章
301瀏覽量
37836
發布評論請先 登錄
相關推薦
評論