女人自慰AV免费观看内涵网,日韩国产剧情在线观看网址,神马电影网特片网,最新一级电影欧美,在线观看亚洲欧美日韩,黄色视频在线播放免费观看,ABO涨奶期羡澄,第一导航fulione,美女主播操b

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

i.MX6ULL驅動開發1—字符設備開發模板

碼農愛學習 ? 來源:碼農愛學習 ? 作者:碼農愛學習 ? 2022-03-17 09:13 ? 次閱讀

之前的幾篇文章(從i.MX6ULL嵌入式Linux開發1-uboot移植初探起),介紹了嵌入式了Linux的系統移植(uboot、內核與根文件系統)以及使用MfgTool工具將系統燒寫到板子的EMMC中。

本篇開始介紹嵌入式Linux驅動開發。

1 Linux驅動分類

Linux中的外設驅動可以分為三大類:字符設備驅動、塊設備驅動和網絡設備驅動。

字符設備驅動:字符設備是能夠按照字節流(比如文件)進行讀寫操作的設備。字符設備最常見,從最簡單的點燈到I2C、SPI、音頻等都屬于字符設備驅動

塊設備驅動:以存儲塊為基礎的設備驅動,如EMMC、NAND、SD卡等。對用戶而言,字符設備與塊設備的訪問方式沒有差別。

網絡設備驅動:即網絡驅動,它同時具有字符設備和塊設備的特點,因為它是輸入輸出是有結構塊的(報文,包,幀),但它的塊的大小又不是固定的。

poYBAGIx_3KAJV1gAAB19ZrvOjc355.png

2 Linux驅動基本原理

Linux中一切皆文件,驅動加載成功以后會在“/dev”目錄下生成一個相應的文件,應用程序通過對這個名為“/dev/xxx”的文件進行相應的操作即可實現對硬件的操作。

比如最簡單的點燈功能,會有/dev/led這樣的驅動文件,應用程序使用open函數來打開文件/dev/led,如果要點亮或關閉led,那么就使用write函數寫入開關值,如果要獲取led的狀態,就用read函數從驅動中讀取相應的狀態,使用完成以后使用close函數關閉/dev/led這個文件。

2.1 Linux軟件分層結構

Linux軟件從上到下可以分層4層結構,以控制LED為例:

應用層:應用程序使用庫提供的open函數打開LED設備

:庫根據open函數傳入的參數執行“swi”指令,進而引起CPU異常,進入內核

內核:內核的異常處理函數根據傳入的參數找到對應的驅動程序,返回文件句柄給庫,進而返回給應用層

應用層得到文件句柄后,使用庫提供的write或ioctl發出控制指令

庫根據write或ioctl函數傳入的參數執行“swi”指令,進入內核

內核的異常處理函數根據傳入的參數找到對應的驅動程序

驅動:驅動程序控制硬件,點亮LED

應用程序運行在用戶空間,而Linux驅動屬于內核的一部分,因此驅動運行于內核空間。當應用層通過open函數打開/dev/led 這個驅動時,因用戶空間不能直接操作內核,因此會使用“系統調用”的方法來從用戶空間“陷入”到內核空間,實現對底層驅動的操作。

pYYBAGIx_3yAY25pAAA6Q-W_Aa8447.png

比如應用程序調用了open這個函數,則在驅動程序中也應有一個對應的open的函數。

2.2 Linux內核驅動操作函數

每一個系統調用,在驅動中都有與之對應的一個驅動函數,在Linux內核文件include/linux/fs.h中有個file_operations結構體,就是Linux內核驅動操作函數集合:

struct file_operations {
	struct module *owner;
	loff_t (*llseek) (struct file *, loff_t, int);
	ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
	ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
	ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
	ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
	int (*iterate) (struct file *, struct dir_context *);
	unsigned int (*poll) (struct file *, struct poll_table_struct *);
	long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
	long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
	int (*mmap) (struct file *, struct vm_area_struct *);
	int (*mremap)(struct file *, struct vm_area_struct *);
	int (*open) (struct inode *, struct file *);
	int (*flush) (struct file *, fl_owner_t id);
	int (*release) (struct inode *, struct file *);
	int (*fsync) (struct file *, loff_t, loff_t, int datasync);
	int (*aio_fsync) (struct kiocb *, int datasync);
	int (*fasync) (int, struct file *, int);
	/*省略若干行...*/
};

其中有關字符設備驅動開發中常用的函數有:

owner:擁有該結構體的模塊的指針,一般設置為THIS_MODULE。

llseek函數:用于修改文件當前的讀寫位置。

read函數:用于讀取設備文件。

write函數:用于向設備文件寫入(發送)數據。

poll函數:是個輪詢函數,用于查詢設備是否可以進行非阻塞的讀寫。

unlocked_ioctl函數:提供對于設備的控制功能, 與應用程序中的 ioctl 函數對應。

compat_ioctl函數:與 unlocked_ioctl功能一樣,區別在于在 64 位系統上,32 位的應用程序調用將會使用此函數。在 32 位的系統上運行 32 位的應用程序調用的是unlocked_ioctl。

mmap函數:用于將將設備的內存映射到進程空間中(也就是用戶空間),一般幀緩沖設備會使用此函數, 比如 LCD 驅動的顯存,將幀緩沖(LCD 顯存)映射到用戶空間中以后應用程序就可以直接操作顯存了,這樣就不用在用戶空間和內核空間之間來回復制。

open函數:用于打開設備文件。

release函數:用于釋放(關閉)設備文件,與應用程序中的 close 函數對應。

fasync函數:用于刷新待處理的數據,用于將緩沖區中的數據刷新到磁盤中。

aio_fsync函數:與fasync功能類似,只是 aio_fsync 是異步刷新待處理的

2.3 Linux驅動運行方式

Linux 驅動有兩種運行方式:

將驅動編譯進Linux內核中, 這樣當Linux內核啟動的時候就會自動運行驅動程序。

將驅動編譯成模塊(擴展名為 .ko), 在Linux內核啟動以后使用“insmod”命令加載驅動模塊。

在驅動開發階段一般都將其編譯為模塊,不需要編譯整個Linux代碼,方便調試驅動程序。當驅動開發完成后,根據實際需要,可以選擇是否將驅動編譯進Linux內核中。

2.4 Linux設備號

2.4.1 設備號的組成

Linux中每個設備都有一個設備號,設備號由主設備號和次設備號兩部分組成。

主設備號:表示某一個具體的驅動

次設備號:表示使用這個驅動的各個設備

Linux 提供了名為dev_t的數據類型表示設備號,其本質是32位的unsigned int數據類型,其中高12位為主設備號,低20位為次設備號,因此Linux中主設備號范圍為0~4095

在文件include/linux/kdev_t.h中提供了幾個關于設備號操作的宏定義:

#define MINORBITS   20 

#define MINORMASK   ((1U << MINORBITS) - 1) 

?

#define MAJOR(dev) ?  ((unsigned int) ((dev) >> MINORBITS)) 

#define MINOR(dev)   ((unsigned int) ((dev) & MINORMASK)) 

#define MKDEV(ma,mi)  (((ma) << MINORBITS) | (mi))

MINORBITS:表示次設備號位數,一共20位

MINORMASK:表示次設備號掩碼

MAJOR:用于從dev_t中獲取主設備號,將dev_t右移20位即可

MINOR:用于從dev_t中獲取次設備號,取dev_t的低20位的值即可

MKDEV:用于將給定的主設備號和次設備號的值組合成dev_t類型的設備號

2.4.2 主設備號的分配

主設備號的分配包括靜態分配和動態分配

靜態分配需要手動指定設備號,并且要注意不能與已有的重復,一些常用的設備號已經被Linux內核開發者給分配掉了,使用“cat /proc/devices”命令可查看當前系統中所有已經使用了的設備號。

動態分配是在注冊字符設備之前先申請一個設備號,系統會自動分配一個沒有被使用的設備號, 這樣就避免了沖突。在卸載驅動的時候釋放掉這個設備號即可。

設備號的申請函數:

/*
* dev:保存申請到的設備號
* baseminor:次設備號起始地址,一般baseminor為0 (次設備號以baseminor為起始地址地址開始遞)
* count:要申請的設備號數量
* name:設備名字
*/
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name) 

設備號的釋放函數:

/*
* from:要釋放的設備號
* count:表示從from開始,要釋放的設備號數量
*/
void unregister_chrdev_region(dev_t from, unsigned count) 

3 字符設備驅動開發模板

3.1 加載與卸載

在編寫驅動的時候需要注冊模塊加載和卸載這兩種函數:

module_init(xxx_init);   //注冊模塊加載函數 
module_exit(xxx_exit);   //注冊模塊卸載函數 

module_init()用來向Linux內核注冊一個模塊加載函數,參數xxx_init就是需要注冊的具體函數,當使用 “insmod” 命令加載驅動的時候,xxx_init這個函數就會被調用。

module_exit()用來向Linux內核注冊一個模塊卸載函數,參數xxx_exit就是需要注冊的具體函數,當使 用“rmmod”命令卸載具體驅動的時候 xxx_exit函數就會被調用。

字符設備驅動模塊加載和卸載模板如下所示:

/* 驅動入口函數 */ 
static int __init xxx_init(void) 
{ 
	/*入口函數內容 */ 
	return 0; 
} 

/* 驅動出口函數 */ 
static void __exit xxx_exit(void) 
{ 
	/*出口函數內容*/ 
} 

/*指定為驅動的入口和出口函數 */ 
module_init(xxx_init); 
module_exit(xxx_exit); 

驅動編譯完成以后擴展名為.ko, 有兩種命令可以加載驅動模塊:

insmod:最簡單的模塊加載命令,用于加載指定的.ko模塊,此命令不能解決模塊的依賴關系

modprobe:該命令會分析模塊的依賴關系,將所有的依賴模塊都加載到內核中,因此更智能

modprobe 命令默認會去/lib/modules/目錄中查找模塊(自制的根文件系統沒有這個目錄,需要手動創建)

卸載驅動也有兩種命令:

rmmod:例如使用rmmod drv.ko來卸載 drv.ko這一個模塊

modprobe -r:該命令除了卸載指定的驅動,還卸載其所依賴的其他模塊,若這些依賴模塊還在被其它模塊使用,就不能使用 modprobe來卸載驅動模塊!!!

3.2 注冊與注銷

對于字符設備驅動而言,當驅動模塊加載成功以后需要注冊字符設備,同樣,卸載驅動模塊的時候也需要注銷掉字符設備

字符設備的注冊函數原型如下所示:

/* func: register_chrdev 注冊字符設備
* major:主設備號
* name:設備名字,指向一串字符串
* fops:結構體 file_operations 類型指針,指向設備的操作函數集合變量
*/
static inline int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops) 

字符設備的注銷函數原型如下所示:

/* func: unregister_chrdev 注銷字符設備
* majo:要注銷的設備對應的主設備號 
* name:要注銷的設備對應的設備名
*/
static inline void unregister_chrdev(unsigned int major, const char *name) 

一般字符設備的注冊在驅動模塊的入口函數 xxx_init 中進行,字符設備的注銷在驅動模塊的出口函數 xxx_exit 中進行。

static struct file_operations test_fops;

/* 驅動入口函數 */ 
static int __init xxx_init(void) 
{ 
	/* 入口函數具體內容 */ 
    int retvalue = 0; 
    /* 注冊字符設備驅動 */ 
	retvalue = register_chrdev(200, "chrtest", &test_fops); 
	if(retvalue < 0)
    { 
		/*  字符設備注冊失敗, 自行處理 */ 
	} 
	return 0; 
} 

/* 驅動出口函數 */ 
static void __exit xxx_exit(void) 
{ 
	/* 注銷字符設備驅動 */ 
	unregister_chrdev(200, "chrtest"); 
} 

/* 將上面兩個函數指定為驅動的入口和出口函數 */ 
module_init(xxx_init); 
module_exit(xxx_exit); 

注:選擇沒有被使用的主設備號,可輸入命令“cat /proc/devices”來查看當前已經被使用掉的設備號

3.3 實現設備的具體操作函數

file_operations 結構體就是設備的具體操作函數。

假設對chrtest這個設備有如下兩個要求:

能夠實現打開和關閉操作:需要實現 file_operations 中的openrelease這兩個函數

能夠實現進行讀寫操作:需要實現 file_operations 中的readwrite這兩個函數

首先是 打開(open)、讀取(read)、寫入(write)、釋放(release) 4個基本操作

/*打開設備*/ 
static int chrtest_open(struct inode *inode, struct file *filp) 
{ 
	/*用戶實現具體功能*/
	return 0; 
} 

/*從設備讀取*/ 
static ssize_t chrtest_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt) 
{ 
	/*用戶實現具體功能*/
	return 0; 
} 

/*向設備寫數據*/ 
static ssize_t chrtest_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt) 
{ 
	/*用戶實現具體功能*/
	return 0; 
} 

/*關閉釋放設備*/ 
static int chrtest_release(struct inode *inode, struct file *filp) 
{ 
	 /*用戶實現具體功能*/ 
	return 0; 
} 

然后是 驅動的入口(init)和出口(exit) 函數:

/*文件操作結構體*/
static struct file_operations test_fops = { 
	.owner = THIS_MODULE,    
	.open = chrtest_open, 
	.read = chrtest_read, 
	.write = chrtest_write, 
	.release = chrtest_release, 
}; 

/*驅動入口函數*/ 
static int __init xxx_init(void) 
{ 
	/*入口函數具體內容*/ 
	int retvalue = 0; 

    /*注冊字符設備驅動*/ 
	retvalue = register_chrdev(200, "chrtest", &test_fops); 
	if(retvalue < 0)
    { 
		/*字符設備注冊失敗*/ 
	} 
	return 0; 
}

/*驅動出口函數*/ 
static void __exit xxx_exit(void) 
{
	/*注銷字符設備驅動*/ 
	unregister_chrdev(200, "chrtest"); 
}

/*指定為驅動的入口和出口函數*/ 
module_init(xxx_init); 
module_exit(xxx_exit); 

3.4 添加LICENSE和作者信息

LICENSE是必須添加的,否則編譯時會報錯,作者信息可加可不加。

MODULE_LICENSE()  //添加模塊 LICENSE 信息 
MODULE_AUTHOR()   //添加模塊作者信息

總結一下:

poYBAGIx_5GAAfbZAAEAxs0IOYU711.png

4 字符設備驅動開發實驗

下面以正點原子提供的教程中的chrdevbase這個虛擬設備為例,完整的編寫一個字符設備驅動模塊。chrdevbase不是實際存在的一個設備,只是為了學習字符設備的開發的流程。

4.1 程序編寫

需要分別編寫驅動程序應用程序

注:為了區分兩個程序的打印信息,在驅動程序的打印前都添加“[BSP]”標識,在應用程序的打印前都添加”[APP]“標識

4.1.1 編寫驅動程序

一些定義

#define CHRDEVBASE_MAJOR	200				/*主設備號*/
#define CHRDEVBASE_NAME		"chrdevbase" 	/*設備名*/

static char readbuf[100];		/*讀緩沖區*/
static char writebuf[100];		/*寫緩沖區*/
static char kerneldata[] = {"kernel data!"}; /*內核驅動中的數據,用來測試應用程序讀取該數據*/

打開、關閉、讀取、寫入

/*
 * @description		: 打開設備
 * @param - inode 	: 傳遞給驅動的inode
 * @param - filp 	: 設備文件,file結構體有個叫做private_data的成員變量
 * 					  一般在open的時候將private_data指向設備結構體。
 * @return 			: 0 成功;其他 失敗
 */
static int chrdevbase_open(struct inode *inode, struct file *filp)
{
	printk("[BSP] chrdevbase open!\n");
	return 0;
}

/*
 * @description		: 從設備讀取數據 
 * @param - filp 	: 要打開的設備文件(文件描述符)
 * @param - buf 	: 返回給用戶空間的數據緩沖區
 * @param - cnt 	: 要讀取的數據長度
 * @param - offt 	: 相對于文件首地址的偏移
 * @return 			: 讀取的字節數,如果為負值,表示讀取失敗
 */
static ssize_t chrdevbase_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
	int retvalue = 0;
	
	/* 向用戶空間發送數據 */
	memcpy(readbuf, kerneldata, sizeof(kerneldata));
    
	retvalue = copy_to_user(buf, readbuf, cnt);
	if(retvalue == 0)
    {
		printk("[BSP] kernel senddata ok!\n");
	}
    else
    {
		printk("[BSP] kernel senddata failed!\n");
	}
	
	printk("[BSP] chrdevbase read!\n");
	return 0;
}

/*
 * @description		: 向設備寫數據 
 * @param - filp 	: 設備文件,表示打開的文件描述符
 * @param - buf 	: 要寫給設備寫入的數據
 * @param - cnt 	: 要寫入的數據長度
 * @param - offt 	: 相對于文件首地址的偏移
 * @return 			: 寫入的字節數,如果為負值,表示寫入失敗
 */
static ssize_t chrdevbase_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
	int retvalue = 0;
    
	/* 接收用戶空間傳遞給內核的數據并且打印出來 */
	retvalue = copy_from_user(writebuf, buf, cnt);
	if(retvalue == 0)
    {
		printk("[BSP] kernel recevdata:%s\n", writebuf);
	}
    else
    {
		printk("[BSP] kernel recevdata failed!\n");
	}
	
	printk("[BSP] chrdevbase write!\n");
	return 0;
}

/*
 * @description		: 關閉/釋放設備
 * @param - filp 	: 要關閉的設備文件(文件描述符)
 * @return 			: 0 成功;其他 失敗
 */
static int chrdevbase_release(struct inode *inode, struct file *filp)
{
	printk("[BSP] chrdevbase release!\n");
	return 0;
}

驅動加載與注銷

/*
 * 設備操作函數結構體
 */
static struct file_operations chrdevbase_fops = {
	.owner = THIS_MODULE,	
	.open = chrdevbase_open,
	.read = chrdevbase_read,
	.write = chrdevbase_write,
	.release = chrdevbase_release,
};

/*
 * @description	: 驅動入口函數 
 * @param 		: 無
 * @return 		: 0 成功;其他 失敗
 */
static int __init chrdevbase_init(void)
{
	int retvalue = 0;

	/* 注冊字符設備驅動 */
	retvalue = register_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME, &chrdevbase_fops);
	if(retvalue < 0)
    {
		printk("[BSP] chrdevbase driver register failed\n");
	}
	printk("[BSP] chrdevbase init!\n");
	return 0;
}

/*
 * @description	: 驅動出口函數
 * @param 		: 無
 * @return 		: 無
 */
static void __exit chrdevbase_exit(void)
{
	/* 注銷字符設備驅動 */
	unregister_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME);
	printk("[BSP] chrdevbase exit!\n");
}

/*將上面兩個函數指定為驅動的入口和出口函數*/
module_init(chrdevbase_init);
module_exit(chrdevbase_exit);

最后的LIENSE與作者

/*LICENSE和作者信息*/
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai & xxpcb"); //本篇的程序代碼在“正點原子”左大神提供的代碼上進行修改

4.1.2 編寫應用程序

這里把程序截取為3段分析,首先看開頭

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"

static char usrdata[] = {"usr data!"}; /*應用程序中的數據,用于測試通過驅動訪問寫入內核*/

int main(int argc, char *argv[])
{
	int fd, retvalue;
	char *filename;
	char readbuf[100], writebuf[100];

	if(argc != 3)
    {
		printf("[APP] Error Usage!\n");
		return -1;
	}

    //參數1是驅動的文件名,用來指定驅動的位置
	filename = argv[1];

	//【1】打開驅動文件
	fd  = open(filename, O_RDWR);
	if(fd < 0)
    {
		printf("[APP] Can't open file %s\n", filename);
		return -1;
	}
    printf("[APP] open file: '%s' success\n", filename);

主要是一些頭文件和main函數入口,調用main函數時需要傳入2個參數(實際是3個參數,函數名本身是默認的第0個參數,不需要手動指定),具體作用為:

參數0:argv[0],函數名本身,這里不作用途

參數1:argv[1],filename,這里不作用途

參數2:argv[2],自定義的操作參數,下面函數會講到,1為從驅動文件中讀取,2為向驅動文件中寫入數據

再來看具體操作:

    //【2】從驅動文件讀取數據
	if(atoi(argv[2]) == 1)//參數1表示【讀取】內核中的數據
    { 
		retvalue = read(fd, readbuf, 50);
		if(retvalue < 0)
        {
			printf("[APP] read file '%s' failed!\n", filename);
		}
        else
        {
			/* 讀取成功,打印出讀取成功的數據 */
			printf("[APP] read data:%s\n",readbuf);
		}
	}
    //【3】向設備驅動寫數據
	if(atoi(argv[2]) == 2)//參數2表示向內核中【寫入】數據
    {
		memcpy(writebuf, usrdata, sizeof(usrdata));
		retvalue = write(fd, writebuf, 50);
		if(retvalue < 0)
        {
			printf("[APP] write file %s failed!\n", filename);
		}
        else
        {
            printf("[APP] write data:'%s' to file ok\n", writebuf);
        }
	}

最后是關閉設備

	//【4】關閉設備
	retvalue = close(fd);
	if(retvalue < 0)
    {
		printf("[APP] Can't close file %s\n", filename);
		return -1;
	}
	printf("[APP] close file ok\r\n");

	return 0;
}

關閉即表示不再使用該設備了(若要再使用則重新打開即可),通過關閉驅動文件來實現字符設備驅動的關閉。

4.2 程序編譯

4.2.1 編譯驅動程序

編譯驅動,即編譯chrdevbase.c這個文件為.ko 模塊,使用Makefile來編譯,先創建Makefile:

KERNELDIR := /home/xxpcb/myTest/imx6ull/kernel/nxp_kernel/linux-imx-rel_imx_4.1.15_2.1.0_ga
CURRENT_PATH := $(shell pwd)
obj-m := chrdevbase.o

build: kernel_modules

kernel_modules:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

各行含義:

KERNELDIR:開發板所使用的Linux內核源碼目錄

CURRENT_PATH:當前路徑,通過運行“pwd”命令獲取

obj-m:將 chrdevbase.c 這個文件編譯為chrdevbase.ko模塊

具體的編譯命令:后面的modules表示編譯模塊,-C 表示切換工作目錄到KERNERLDIR目錄,M表示模塊源碼目錄

輸入“make”命令即可編譯,編譯后會出現許多編譯文件

pYYBAGIx_6SALhsxAAFV2abmvkw000.png

注:若直接make編譯報如下錯誤,是因為kernel中沒有指定編譯器和架構,使用了默認的x86平臺編譯報錯。

pYYBAGIx_6uAB8ssAAFRkPfoi04933.png

修改Kernel工程的頂層Makefile,直接定義ARCH和CROSS_COMPILE 這兩個的變量值為 arm 和 arm-linux-gnueabihf-

(內核篇的介紹見:i.MX6ULL嵌入式Linux開發3-Kernel移植)

poYBAGIx_7OAFZGEAAHM8Vq9p9g685.png

4.2.2 編譯應用程序

編譯應用程序不需要內核文件參與,只有一個文件就能編譯,因此直接輸入指令進行編譯:

arm-linux-gnueabihf-gcc chrdevbaseApp.c -o chrdevbaseApp

編譯會生chrdevbaseApp,它是32位LSB格式的ARM版本可執行文件

pYYBAGIx_7qAYyOQAADRLEqdxKc780.png

4.3 測試

上一篇文章(i.MX6ULL嵌入式Linux開發6-系統燒寫到eMM)已經實現了系統移植的打包燒錄工作,系統已經燒錄的EMMC中了。這次我們就直接在這個基礎上進行實驗。

4.3.1 創建驅動模塊目錄

加載驅動模塊,使用的modprobe命令,會從特定的目錄下尋找文件。比如開發板使用的是4.1.15版的Linux內核 ,則是“/lib/modules/4.1.15”這個目錄,這個目錄一般是沒有的,需要根據Linux內核的版本自己創建。

注意這是開發板的文件系統中的路徑,可以通過串口連接進入開發板,通過linux指令創建該目錄。

4.3.2 發送文件到開發板(TFTP傳輸)

此次測試首先需要將ubuntu中編譯的文件傳輸到板子中運行,怎么傳輸呢?可以使用TFTP傳輸服務。

poYBAGIx__mAJAMxAAFqlflsgR8688.png

之前的文章(i.MX6ULL嵌入式Linux開發2-uboot移植實踐)中已經介紹了如何在ubuntu中搭建TFTP服務器

搭建好TFTP服務后,開始傳輸文件到開發板具體的傳輸步驟為:

開發板連接網線,與ubuntu虛擬機處于同一局域網內

確保ubuntu已安裝的TFTP服務,并設置了TFTP服務文件夾

將ubuntu中編譯好的文件復制到ubuntu的TFTP服務文件夾中!!!

mv chrdevbaseApp ~/myTest/tftpboot/
mv chrdevbase.ko ~/myTest/tftpboot/

注:編譯完程序,在傳輸到板子之前,一定要記得把文件先復制到TFTP文件夾中,否則板子獲取到的可能是TFTP文件夾中的舊文件。

開發板的串口中通過如下指令來將ubuntu中的文件傳輸到開發板中

cd /lib/modules/4.1.15   /*確保在要下載文件的目錄中,若已在,則忽略*/
tftp -g -r chrdevbaseApp 192.168.5.101 /*獲取chrdevbaseApp文件*/
tftp -g -r chrdevbase.ko 192.168.5.101 /*獲取chrdevbase.ko文件*/

這里的-g代表get,即下載文件,-r代表remote file,即遠程主機的文件名,然后是要下載的文件名,最后的遠程主機ubuntu的IP地址

輸入該指令后,可以看到文件傳輸進度,如下圖:

poYBAGIyAAGAOFfdAAAnPNVU2dE378.png

4.3.3 開始測試

驅動文件chrdevbase.ko和應用文件chrdevbaseApp傳輸到板子中的/lib/modules/4.1.15目錄后,就可以測試了。

首先使用insmod命令來加載驅動,然后使用lsmod查看當前的驅動(只有一個我們剛加載的字符驅動),再使用使用cat指令查看devices 信息,確認系統中是否已經列舉了該設備,3條指令如下:

insmod chrdevbase.ko 
lsmod
cat /proc/devices 

具體是輸出信息:

pYYBAGIyAAmAFyp0AAB4iFrzgsc420.png

可以看出,系統中存在chrdevbase設備,主設備號為程序中設定的200。

驅動加載后,還要在/dev目錄下創建一個對應的設備節點文件(應用程序就是通過該節點文件實現對設備的操作)。

輸入如下2條命令創建/dev/chrdevbase這個設備節點文件,并查看結果:

mknod /dev/chrdevbase c 200 0 
ls /dev/chrdevbase -l
pYYBAGIyAA-AX6J5AAAy_IcL8LE995.png

至此,字符設備驅動已經加載完成,可以測試我們的應用程序了,也就是

按照上面程序的設定,1是讀,2是寫:

./chrdevbaseApp /dev/chrdevbase 1  
./chrdevbaseApp /dev/chrdevbase 2

先來看“讀測試”,注意要給chrdevbaseApp可執行的權限,否則無法運行。

poYBAGIyABWAU-ruAADTDlIHaAc973.png

圖中下部是程序輸出信息,但似乎只有BSP驅動程序的的輸出,沒有APP應用程序的輸出,應該是內核打印printk與應用的打印printf沖突了,導致APP的打印被擠掉了。

再來看“寫測試'',同樣也是只有BSP的打印

pYYBAGIyABuAGx8pAAAVuxovM8w140.png

4.3.4 打印沖突問題規避

對于打印沖突問題,我們可以先在每個printf前后加個sleep(1)的1秒延時,這樣可以先避免打印沖突。

增加延時后再次測試,打印正常:

poYBAGIyACKACZljAAB8A9RNG8I614.png

測試完,最后是rmmod命令卸載模塊:

pYYBAGIyACaAAjSlAAAKmjegX_U693.png

5 總結

本篇介紹了嵌入式Linux驅動開發中的基礎驅動——字符驅動開發的基本模式,使用了一個虛擬的字符設備驅動進行測試,了解驅動程序與應用程序之間的調用關系。
審核編輯:湯梓紅

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 設備
    +關注

    關注

    2

    文章

    4635

    瀏覽量

    71452
  • 模板
    +關注

    關注

    0

    文章

    109

    瀏覽量

    20822
收藏 人收藏

    評論

    相關推薦
    熱點推薦

    i.MX6ULL 驅動開發7—按鍵輸入捕獲與GPIO輸入配置與高低電平讀取

    本篇主要介紹了i.MX6ULL的按鍵檢測的使用,主要的知識點是設備樹的修改,以及GPIO的輸入配置與高低電平的讀取。
    的頭像 發表于 05-24 09:11 ?6763次閱讀
    <b class='flag-5'>i.MX6ULL</b> <b class='flag-5'>驅動</b><b class='flag-5'>開發</b>7—按鍵輸入捕獲與GPIO輸入配置與高低電平讀取

    i.MX6ULL嵌入式Linux開發1-uboot移植初探

    本系列教程以i.MX6ULL處理器的ARM開發板為實驗基礎,學習記錄嵌入式Linux開發的各種知識與經驗,主要內容包括嵌入式Linux移植,嵌入式Linux驅動
    的頭像 發表于 03-07 08:57 ?4211次閱讀
    <b class='flag-5'>i.MX6ULL</b>嵌入式Linux<b class='flag-5'>開發</b><b class='flag-5'>1</b>-uboot移植初探

    i.MX6ULL驅動開發2—新字符設備開發模板

    上篇文章介紹了字符設備開發模板,但那是一種舊版本的驅動開發模式,
    的頭像 發表于 03-17 09:11 ?3485次閱讀
    <b class='flag-5'>i.MX6ULL</b><b class='flag-5'>驅動</b><b class='flag-5'>開發</b>2—新<b class='flag-5'>字符</b><b class='flag-5'>設備</b><b class='flag-5'>開發</b><b class='flag-5'>模板</b>

    使用i.MX6ULL開發板進行Linux根文件系統的完善

    上一篇推文講了怎么移植根文件系統,并在i.MX6ULL開發板中運行起來,但是會出現一些提示,現在來進行根文件的完善。
    發表于 10-17 11:13 ?948次閱讀

    移植NXP官方linux 5.4內核到i.MX6ULL開發

    本文描述移植NXP官方 linux 5.4 內核到i.MX6ULL開發板。
    發表于 12-19 11:10 ?2320次閱讀

    I.MX6ULL終結者開發板裸機仿真jlink調試

    I.MX6ULL‘終結者’開發板預留了JTAG仿真接口,并給出了開發文檔,可以實現在JLINK仿真器條件下的單步跟蹤、斷點調試等功能,使得開發研究i
    發表于 07-07 10:56

    i.MX6ULL開發板硬件資源

    迅為i.MX6ULL 終結者開發板硬件資源非常豐富,幾乎將 i.MX6ULL 芯片的所有資源都擴展引出到底板上了,底板提供了豐富的外設接口,開發板的尺寸是 190mm*125mm,充分
    發表于 12-29 06:18

    初識 i.MX6ULL 寄存器

    裸機開發_L1_匯編LED實驗0. 本節目標1. 硬件層電路2. 初識 i.MX6ULL 寄存器2.1 i.MX6ULL 時鐘控制寄存器2.
    發表于 12-20 07:13

    飛凌i.MX6ULL開發板的評測,再次進階擁有更高的性價比

    處理器MCIMX6Y2開發設計,采用先進的ARMCortex-A7內核,運行速度高達800MHz。i.MX6ULL應用處理器包括一個集成的電源管理模塊,降低了外接電源的復雜性,并簡化了上電時序。
    發表于 10-27 11:55 ?1648次閱讀
    飛凌<b class='flag-5'>i.MX6ULL</b><b class='flag-5'>開發</b>板的評測,再次進階擁有更高的性價比

    基于NXP i.MX6ULL處理器的FETMX6ULL-C核心板

    合作伙伴,飛凌不負美譽,基于i.MX6ULL匠心打造的FETMX6ULL-S核心板一經問世便好評不斷,且已有數百家來自工業、醫療、電力、物聯網等行業的用戶采用此款核心板快速完成了整機產品的開發上市。
    發表于 04-11 15:05 ?1276次閱讀
    基于NXP <b class='flag-5'>i.MX6ULL</b>處理器的FETMX<b class='flag-5'>6ULL</b>-C核心板

    i.MX6ULL驅動開發4——點亮LED(寄存器版)

    本篇主要介紹了如何通過操作寄存器來點亮i.MX6ULL開發板上的led,通過編寫LED對應的驅動程序和應用程序,實現程序設計的分層。
    的頭像 發表于 05-21 21:26 ?3258次閱讀
    【<b class='flag-5'>i.MX6ULL</b>】<b class='flag-5'>驅動</b><b class='flag-5'>開發</b>4——點亮LED(寄存器版)

    i.MX6ULL|字符設備驅動開發實踐

    字符設備驅動開發的基本步驟可以看上一篇,本節就以 chrdevbase 這個虛擬設備為例,完整的編寫一個
    的頭像 發表于 10-31 11:27 ?819次閱讀

    【北京迅為】i.MX6ULL開發板移植 Debian 文件系統

    【北京迅為】i.MX6ULL開發板移植 Debian 文件系統
    的頭像 發表于 02-10 15:34 ?1433次閱讀
    【北京迅為】<b class='flag-5'>i.MX6ULL</b><b class='flag-5'>開發</b>板移植 Debian 文件系統

    基于i.MX6ULL的掉電檢測設計與軟件測試

    基于i.MX6ULL的掉電檢測設計與軟件測試基于i.MX6ULL平臺設計實現掉電檢測功能,首先選擇一路IO,利用IO電平變化觸發中斷,在編寫驅動時捕獲該路GPIO的中斷,然后在中斷響應函數中發
    的頭像 發表于 11-09 10:40 ?1110次閱讀
    基于<b class='flag-5'>i.MX6ULL</b>的掉電檢測設計與軟件測試

    【迅為電子】i.MX6UL和i.MX6ULL芯片區別與開發板對比

    【迅為電子】i.MX6UL和i.MX6ULL芯片區別與開發板對比
    的頭像 發表于 11-28 14:31 ?1212次閱讀
    【迅為電子】<b class='flag-5'>i.MX6</b>UL和<b class='flag-5'>i.MX6ULL</b>芯片區別與<b class='flag-5'>開發</b>板對比