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