天道酬勤,学无止境

嵌入式linux驱动开发-字符设备驱动

驱动

  • 字符设备驱动简介
  • 字符设备驱动开发步骤
    • 驱动模块的加载和卸载
    • 字符设备注册与注销
    • 实现设备的具体操作函数
    • 添加 LICENSE 和作者信息
    • 总结
  • Linux 设备号
    • 设备号的组成
    • 设备号的分配
  • chrdevbase 字符设备驱动开发实验
    • 实验程序编写
      • 驱动程序chrdevbase.c
      • 用户程序chrdevbaseApp.c
    • 编译驱动程序和测试 APP
    • 总结

之前在嵌入式系统移植停留了一段时间,枯燥地看uboot、kernel和跟文件系统的源码感觉没有成就感,在这之前已经对嵌入式linux的启动流程有了大体的了解,因此先跳到驱动开发,体验学习一下子哈!!

Linux 中的三大类驱动:字符设备驱动、块设备驱动和网络设备驱动

字符设备驱动:IO口的驱动,比如点灯、 I2C、 SPI、音频等。
块设备驱动:存储器设备的驱动,比如 EMMC、 NAND、 SD 卡和 U 盘等存储设备
网络设备驱动:网络驱动,比如 USB WIFI,其使用 USB 接口,所以属于字符设备,但是其又能上网,所以也属于网络设备驱动。



Linux 下的应用程序是如何调用驱动程序的?
在这里插入图片描述
在 Linux 中一切皆为文件,驱动加载成功以后会在“/dev”目录下生成一个相应的文件,应用程序通过对这个名为“/dev/xxx” (xxx 是具体的驱动文件名字)的文件进行相应的操作即可实现对硬件的操作

比如, led 灯/dev/led 的驱动文件,应用程序使用open()、close()、write()、read()等函数对文件操作,进而控制硬件。



字符设备驱动简介

应用程序运行在用户空间,而 Linux 驱动属于内核的一部分,因此驱动运行于内核空间。当我们在用户空间想要实现对内核的操作,比如使用 open 函数打开/dev/led 这个驱动,因为用户空间不能直接对内核进行操作,因此必须使用一个叫做“系统调用”的方法来实现从用户空间“陷入” 到内核空间,这样才能实现对底层驱动的操作。

在这里插入图片描述
如上图,从应用程序到具体驱动程序都有相应的函数与之对应,比如调用open 这个函数。

内核部分: Linux 内核文件 include/linux/fs.h 中有个叫做 file_operations 的结构体,此结构体就是 Linux 内核驱动操作函数集合。(听名字就知道是管理驱动文件操作的)

1588 struct file_operations {
1589 	struct module *owner;
1590 	loff_t (*llseek) (struct file *, loff_t, int);
1591 	ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
1592 	ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
1593 	ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
1594 	ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
1595 	int (*iterate) (struct file *, struct dir_context *);
1596 	unsigned int (*poll) (struct file *, struct poll_table_struct *);
1597 	long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
1598 	long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
1599 	int (*mmap) (struct file *, struct vm_area_struct *);
1600 	int (*mremap)(struct file *, struct vm_area_struct *);
1601 	int (*open) (struct inode *, struct file *);
1602 	int (*flush) (struct file *, fl_owner_t id);
1603 	int (*release) (struct inode *, struct file *);
1604 	int (*fsync) (struct file *, loff_t, loff_t, int datasync);
1605 	int (*aio_fsync) (struct kiocb *, int datasync);
......
1618 #ifndef CONFIG_MMU
1619 	unsigned (*mmap_capabilities)(struct file *);
1620 #endif
1621 };

file_operation 结构体中比较重要的、常用的函数:
第 1589 行, owner 拥有该结构体的模块的指针,一般设置为 THIS_MODULE。
第 1590 行, llseek 函数用于修改文件当前的读写位置。
第 1591 行, read 函数用于读取设备文件
第 1592 行, write 函数用于向设备文件写入(发送)数据
第 1596 行, poll 是个轮询函数,用于查询设备是否可以进行非阻塞的读写。
第 1597 行, unlocked_ioctl 函数提供对于设备的控制功能,与应用程序中的 ioctl 函数对应。
第 1598 行, compat_ioctl 函数与 unlocked_ioctl 函数功能一样,区别在于在 64 位系统上,32 位的应用程序调用将会使用此函数。在 32 位的系统上运行 32 位的应用程序调用的是unlocked_ioctl。
第 1599 行, mmap 函数用于将设备的内存映射到进程空间中(也就是用户空间),一般帧缓冲设备会使用此函数,比如 LCD 驱动的显存,将帧缓冲(LCD 显存)映射到用户空间中以后应用程序就可以直接操作显存了,这样就不用在用户空间和内核空间之间来回复制。
第 1601 行, open 函数用于打开设备文件。
第 1603 行, release 函数用于释放(关闭)设备文件,与应用程序中的 close 函数对应。
第 1604 行, fasync 函数用于刷新待处理的数据,用于将缓冲区中的数据刷新到磁盘中。
第 1605 行, aio_fsync 函数与 fasync 函数的功能类似,只是 aio_fsync 是异步刷新待处理的数据。

字符设备驱动开发步骤

在 Linux 驱动开发中肯定也是要初始化相应的外设寄存器, Linux 驱动开发中我们需要按照其规定的框架来编写驱动,所以说学 Linux 驱动开发重点是学习其驱动框架。

驱动模块的加载和卸载

Linux 驱动有两种运行方式:
第一种就是将驱动编译进 Linux 内核中,这样当 Linux 内核启动的时候就会自动运行驱动程序。
第二种就是将驱动编译成模块(Linux 下模块扩展名为.ko),在Linux 内核启动以后使用“insmod”命令加载驱动模块。

调试时推荐使用将驱动编译成模块,在调试驱动的时候一般都选择将其编译为模块,这样我们修改驱动以后只需要编译一下驱动代码即可,不需要编译整个 Linux 代码。而且在调试的时候只需要加载或者卸载驱动模块即可,不需要重启整个系统。
驱动开发完成可以将驱动编译进Linux 内核中。

模块有加载卸载两种操作,

module_init(xxx_init); //注册模块加载函数
module_exit(xxx_exit); //注册模块卸载函数

module_init 函数用来向 Linux 内核注册一个模块加载函数,参数 xxx_init 就是需要注册的具体函数,当使用“insmod”命令加载驱动的时候, xxx_init 这个函数就会被调用。
module_exit()函数用来向 Linux 内核注册一个模块卸载函数,参数 xxx_exit 就是需要注册的具体函数,当使用“rmmod”命令卸载具体驱动的时候 xxx_exit 函数就会被调用。

字符设备驱动模块加载和卸载模板如下所示:

1 /* 驱动入口函数 */
2 static int __init xxx_init(void)
3 {
4 		/* 入口函数具体内容 */
5 		return 0;
6 }
7 8
/* 驱动出口函数 */
9 static void __exit xxx_exit(void)
10 {
11 		/* 出口函数具体内容 */
12 }
13
14 /* 将上面两个函数指定为驱动的入口和出口函数 */
15 module_init(xxx_init);
16 module_exit(xxx_exit);

驱动编译完成以后扩展名为.ko的模块,终端命令常用insmod、rmmod,另外modprobe命令可以将具有依赖关系的模块一锅端的加载和卸载

//加载驱动模块
insmod drv.ko
或 modprobe drv.ko
//卸载驱动模块
rmmod drv.ko
或 modprobe -r drv.ko

字符设备注册与注销

对于字符设备驱动而言,当驱动模块加载成功以后需要注册字符设备,同样,卸载驱动模块的时候也需要注销掉字符设备
(就是你不仅要加载自己的驱动模块,还要向内核注册申请,这样才能是合法公民)

字符设备的注册和注销函数原型:

static inline int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops)
static inline void unregister_chrdev(unsigned int major, const char *name)

major: 主设备号, Linux 下每个设备都有一个设备号,设备号分为主设备号和次设备号两部分,关于设备号后面会详细讲解。
name:设备名字,指向一串字符串。
fops: 结构体 file_operations 类型指针,指向设备的操作函数集合变量。

注册函数register_chrdev()位于加载函数调用的驱动入口函数中;
注销函数unregister_chrdev()位于卸载函数调用的驱动出口函数中。

37 /* 驱动入口函数 */
38 static int __init xxx_init(void)
39 {
40 		/* 入口函数具体内容 */
41 		int retvalue = 0;
42
43 		/* 注册字符设备驱动 */
44 		retvalue = register_chrdev(200, "chrtest", &test_fops);
45 		if(retvalue < 0){
46 				/* 字符设备注册失败,自行处理 */
47 		}
48 		return 0;
49 }
50
51 /* 驱动出口函数 */
52 static void __exit xxx_exit(void)
53 {
54 		/* 注销字符设备驱动 */
55 		unregister_chrdev(200, "chrtest");
56 }

设备号不能和其他驱动文件冲突,可使用命令查看当前已经被使用掉的设备号:

cat /proc/devices

实现设备的具体操作函数

比如,对chrtest 设备进行驱动,对file_operations结构体类型的变量 test_fops进行初始化,内部存有驱动文件操作函数,用什么操作函数就添加什么操作函数

打开和关闭操作:最基本的要求;
读写操作:拥有读写一段缓冲区(内存)。

1 /* 打开设备 */
2 static int chrtest_open(struct inode *inode, struct file *filp)
3 {
4 		/* 用户实现具体功能 */
5 		return 0;
6 }
7 
8 /* 从设备读取 */
9 static ssize_t chrtest_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
10 {
11 		/* 用户实现具体功能 */
12 		return 0;
13 }
14
15 /* 向设备写数据 */
16 static ssize_t chrtest_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
17 {
18 		/* 用户实现具体功能 */
19 		return 0;
20 }
21
22 /* 关闭/释放设备 */
23 static int chrtest_release(struct inode *inode, struct file *filp)
24 {
25 		/* 用户实现具体功能 */
26 		return 0;
27 }
28//初始化操作函数结构体变量
29 static struct file_operations test_fops = {
30 		.owner = THIS_MODULE,
31 		.open = chrtest_open,
32 		.read = chrtest_read,
33 		.write = chrtest_write,
34 		.release = chrtest_release,
35 };

添加 LICENSE 和作者信息

LICENSE 是必须添加的,否则的话编译的时候会报错,LICENSE 采用 GPL 协议。
作者信息可以添加也可以不添加。

MODULE_LICENSE("GPL") //添加模块 LICENSE 信息
MODULE_AUTHOR("zxy") //添加模块作者信息

总结

一个完整的字符设备驱动模板就上以上4条:

/*1、文件操作函数file_operations结构体 变量初始化*/
......
/*2、驱动入口函数注册 驱动出口函数注销*/
......
/*3、加载驱动模块 卸载驱动模块*/
......
/*4、license和作者信息*/
(执行流程:3调用22调用1

Linux 设备号

设备号的组成

Linux 中每个设备都有一个设备号,设备号由主设备号次设备号两部分组成,
主设备号表示某一个具体的驱动,
次设备号表示使用这个驱动的各个设备。

设备号由dev_t数据类型表示

typedef __kernel_dev_t dev_t;
......
typedef __u32 __kernel_dev_t;
......
typedef unsigned int __u32;
......
经过来回的定义,设备号就是c语言中unsigned int32位数据类型;
其中高 12 位为主设备号, 低 20 位为次设备号。

文件 include/linux/kdev_t.h 中提供了几个关于设备号的操作函数(本质是宏函数):

6 #define MINORBITS 20
7 #define MINORMASK ((1U << MINORBITS) - 1)
8 
9 #define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
10 #define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
11 #define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))

第 6 行,宏 MINORBITS 表示次设备号位数,一共是 20 位。
第 7 行,宏 MINORMASK 表示次设备号掩码。
第 9 行,宏 MAJOR 用于从 dev_t 中获取主设备号,将 dev_t 右移 20 位即可。
第 10 行,宏 MINOR 用于从 dev_t 中获取次设备号,取 dev_t 的低 20 位的值即可。
第 11 行,宏 MKDEV 用于将给定的主设备号和次设备号的值组合成 dev_t 类型的设备号。

设备号的分配

1、静态分配设备号
使用“cat /proc/devices”命令即可查看当前系统中所有已经使用了的设备号,人为选择没有使用的设备号。
2、动态分配设备号
Linux 社区推荐使用动态分配设备号,在注册字符设备之前先申请一个设备号,系统自动给你一个没有被使用的设备号,这样就避免了冲突。
设备号的申请函数如下:

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)

dev:	保存申请到的设备号。
baseminor: 次设备号起始地址, alloc_chrdev_region 可以申请一段连续的多个设备号,这些设备号的主设备号一样,但是次设备号不同,次设备号以 baseminor 为起始地址地址开始递增。一般 baseminor 为 0,也就是说次设备号从 0 开始。
count: 要申请的设备号数量。
name:	设备名字。

设备号释放函数如下:

void unregister_chrdev_region(dev_t from, unsigned count)

from:要释放的设备号。
count: 表示从 from 开始,要释放的设备号数量。

chrdevbase 字符设备驱动开发实验

为了完整的编写一个字符设备驱动模块,假设一个虚拟设备: chrdevbase
设备描述:chrdevbase 设备有两个缓冲区,一个为读缓冲
区,一个为写缓冲区,这两个缓冲区的大小都为 100 字节。
应用程序描述:在应用程序中可以向 chrdevbase 设备的写缓冲区中写入数据,从读缓冲区中读取数据。

实验程序编写

实现功能描述:
应用程序调用 open 函数打开 chrdevbase 这个设备,
使用 write 函数向chrdevbase 的写缓冲区 writebuf 中写入数据(不超过 100 个字节),
使用 read 函数读取读缓冲区 readbuf 中的数据操作,
应用程序使用 close 函数关闭 chrdevbase 设备。

驱动程序chrdevbase.c

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
/***************************************************************
Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
文件名		: chrdevbase.c
作者	  	: 左忠凯
版本	   	: V1.0
描述	   	: chrdevbase驱动文件。
其他	   	: 无
论坛 	   	: www.openedv.com
日志	   	: 初版V1.0 2019/1/30 左忠凯创建
***************************************************************/

#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("chrdevbase open!\r\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("kernel senddata ok!\r\n");
	}else{
		printk("kernel senddata failed!\r\n");
	}
	
	//printk("chrdevbase read!\r\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("kernel recevdata:%s\r\n", writebuf);
	}else{
		printk("kernel recevdata failed!\r\n");
	}
	
	//printk("chrdevbase write!\r\n");
	return 0;
}

/*
 * @description		: 关闭/释放设备
 * @param - filp 	: 要关闭的设备文件(文件描述符)
 * @return 			: 0 成功;其他 失败
 */
static int chrdevbase_release(struct inode *inode, struct file *filp)
{
	//printk("chrdevbase release!\r\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("chrdevbase driver register failed\r\n");
	}
	printk("chrdevbase init!\r\n");
	return 0;
}

/*
 * @description	: 驱动出口函数
 * @param 		: 无
 * @return 		: 无
 */
static void __exit chrdevbase_exit(void)
{
	/* 注销字符设备驱动 */
	unregister_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME);
	printk("chrdevbase exit!\r\n");
}

/* 
 * 将上面两个函数指定为驱动的入口和出口函数 
 */
module_init(chrdevbase_init);
module_exit(chrdevbase_exit);

/* 
 * LICENSE和作者信息
 */
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");

分析:文件架构



头文件调用的都是linux内核文件;
4个驱动文件操作函数;
驱动文件操作函数结构体初始化;

驱动入口函数,注册设备;
驱动出口函数,注销设备;

加载驱动模块
卸载驱动模块

许可证
人名


printk函数 相当于 printf 的孪生兄妹, printf运行在用户态 printk 运行在内核态,printk 可以根据日志级别对消息进行分类,一共有 8 个消息级别,这 8 个消息级别定义在文件include/linux/kern_levels.h。

#define KERN_SOH "\001"
#define KERN_EMERG KERN_SOH "0" /* 紧急事件,一般是内核崩溃 */
#define KERN_ALERT KERN_SOH "1" /* 必须立即采取行动 */
#define KERN_CRIT KERN_SOH "2" /* 临界条件,比如严重的软件或硬件错误*/
#define KERN_ERR KERN_SOH "3" /* 错误状态,一般设备驱动程序中使用KERN_ERR 报告硬件错误 */
#define KERN_WARNING KERN_SOH "4" /* 警告信息,不会对系统造成严重影响 */
#define KERN_NOTICE KERN_SOH "5" /* 有必要进行提示的一些信息 */
#define KERN_INFO KERN_SOH "6" /* 提示性的信息 */
#define KERN_DEBUG KERN_SOH "7" /* 调试信息*/

比如:
printk(KERN_EMERG "gsmi: Log Shutdown Reason\n");	//0级别
printk("gsmi: Log Shutdown Reason\n");	//MESSAGE_LOGLEVEL_DEFAULT 默认级别为4

参数 filp 有个叫做 private_data 的成员变量, private_data 是个 void 指针,一般在驱动中将private_data 指向设备结构体,设备结构体会存放设备的一些属性。



用户程序chrdevbaseApp.c


C 库文件操作基本函数
编写测试 APP 就是编写 Linux 应用,需要用到 C 库里面和文件操作有关的一些函数,比如open、 read、 write 和 close 这四个函数。
①、 open 函数

int open(const char *pathname, int flags)

pathname:要打开的设备或者文件名。
flags: 文件打开模式,以下三种模式必选其一:
	O_RDONLY 只读模式
	O_WRONLY 只写模式
	O_RDWR 读写模式
	另外,<逻辑或> 来选择多种模式:
	O_APPEND 每次写操作都写入文件的末尾
	O_CREAT 如果指定文件不存在,则创建这个文件
	O_EXCL 如果要创建的文件已存在,则返回 -1,并且修改 errno 的值
	O_TRUNC 如果文件存在,并且以只写/读写方式打开,则清空文件全部内容
	O_NOCTTY 如果路径名指向终端设备,不要把这个设备用作控制终端。
	O_NONBLOCK 如果路径名指向 FIFO/块文件/字符文件,则把文件的打开和后继I/O 设置为非阻塞
	DSYNC 等待物理 I/O 结束后再 write。在不影响读取新写入的数据的前提下,不等待文件属性更新。
	O_RSYNC read 等待所有写入同一区域的写操作完成后再进行。
	O_SYNC 等待物理 I/O 结束后再 write,包括更新文件属性的 I/O。

返回值:如果文件打开成功的话返回文件的文件描述符。

在 Ubuntu 中输入“man 2 open” 即可查看 open 函数的详细内容

②、close 函数

int close(int fd);

fd:要关闭的文件描述符。
返回值: 0 表示关闭成功,负值表示关闭失败。

③、read 函数
当然是从文件里读内容啦~需要open函数返回的文件描述符。

ssize_t read(int fd, void *buf, size_t count)

fd:要读取的文件描述符,读取文件之前要先用 open 函数打开文件, open 函数打开文件成功以后会得到文件描述符。
buf: 数据读取到此 buf 中。
count: 要读取的数据长度,也就是字节数。
返回值: 读取成功的话返回读取到的字节数;如果返回 0 表示读取到了文件末尾;如果返回负值,表示读取失败。

在 Ubuntu 中输入“man 2 read”命令即可查看 read 函数的详细内容。

④、write 函数

ssize_t write(int fd, const void *buf, size_t count);

fd:要进行写操作的文件描述符,写文件之前要先用 open 函数打开文件, open 函数打开文件成功以后会得到文件描述符。
buf: 要写入的数据。
count: 要写入的数据长度,也就是字节数。
返回值: 写入成功的话返回写入的字节数;如果返回 0 表示没有写入任何数据;如果返回负值,表示写入失败。


编写一个简单的测试 APP,控制驱动文件,测试 APP 运行在用户空间,测试 APP 很简单通过输入相应的指令来对 chrdevbase 设备执行读或者写操作。

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
/***************************************************************
Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
文件名		: chrdevbaseApp.c
作者	  	: 左忠凯
版本	   	: V1.0
描述	   	: chrdevbase驱测试APP。
其他	   	: 使用方法:./chrdevbase /dev/chrdevbase <1>|<2>
  			 argv[2] 1:读文件
  			 argv[2] 2:写文件		
论坛 	   	: www.openedv.com
日志	   	: 初版V1.0 2019/1/30 左忠凯创建
***************************************************************/

static char usrdata[] = {"usr data!"};   //写入的用户数据

/*
 * @description		: main主程序
 * @param - argc 	: argv数组元素个数
 * @param - argv 	: 具体参数
 * @return 			: 0 成功;其他 失败
 */
int main(int argc, char *argv[])
{
	int fd, retvalue;
	char *filename;
	char readbuf[100], writebuf[100];
	
	//要求只能传入3个参数
	if(argc != 3){
		printf("Error Usage!\r\n");
		return -1;
	}

	filename = argv[1];

	/* 打开驱动文件 获取文件描述符*/
	fd  = open(filename, O_RDWR);
	if(fd < 0){
		printf("Can't open file %s\r\n", filename);
		return -1;
	}

	if(atoi(argv[2]) == 1){ /* 从驱动文件读取数据 */
		retvalue = read(fd, readbuf, 50);
		if(retvalue < 0){
			printf("read file %s failed!\r\n", filename);
		}else{
			/*  读取成功,打印出读取成功的数据 */
			printf("read data:%s\r\n",readbuf);
		}
	}

	if(atoi(argv[2]) == 2){
 	/* 向设备驱动写数据 */
		memcpy(writebuf, usrdata, sizeof(usrdata));	//内存复制函数,从一个缓冲区到另一个缓冲区
		retvalue = write(fd, writebuf, 50);
		if(retvalue < 0){
			printf("write file %s failed!\r\n", filename);
		}
	}

	/* 关闭设备 */
	retvalue = close(fd);
	if(retvalue < 0){
		printf("Can't close file %s\r\n", filename);
		return -1;
	}

	return 0;
}




分析:程序架构
调用C库函数;
打开文件;
读写文件;
关闭文件。


判断 argv[2]参数的值是 1 或 2 ,因为输入命令的时候其参数都是字符串格式的,因此需要借助 atoi 函数将字符串格式的数字转换为真实的数字



编译驱动程序和测试 APP

1、编译驱动程序chrdevbase.c 这个文件,将其编译为.ko 模块,创建Makefile 文件,路径自己改改:

KERNELDIR := /home/zxy/linux/kernel_lib/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek
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 内核源码目录(原子官方linux内核源码,需要编译了好久),使用绝对路径,大家根据自己的实际情况填写即可。
CURRENT_PATH 表示当前路径,直接通过运行“pwd”命令来获取当前所处路径。
obj-m 表示将 chrdevbase.c 这个文件编译为 chrdevbase.ko 模块。
后面的modules 表示编译模块,
-C 表示将当前的工作目录切换到指定目录中,也就是 KERNERLDIR 目录。
M 表示模块源码目录,“make modules”命令中加入 M=dir 以后程序会自动到指定的 dir 目录中读取模块的源码并将其编译为.ko 文件

2、编译 chrdevbaseApp.c生成可执行程序,这里差不多就是单纯的C语言程序,用交叉编译器arm-linux-gnueabihf-gcc编译即可:

//编译
arm-linux-gnueabihf-gcc chrdevbaseApp.c -o chrdevbaseApp
//查看文件信息,chrdevbaseAPP 这个可执行文件是 32 位 LSB 格式, ARM 版本的,因此 chrdevbaseAPP 只能在 ARM 芯片下运行。
file chrdevbaseApp

3、传文件到开发板验证
左老师使用的 TFTP 从网络启动,并且使用 NFS 挂载网络根文件系统,说白了就是从uboot层面网络启动linux内核和挂在根文件系统,但是这能保存到DRAM中。
本人这里研究了一下ssh传输协议,实现linux系统client和server之间的文件互传:
1、两者连接在同一网络地址;
2、服务器安装并配置ssh server协议;
3、板子可以直接scp获取文件啦。
就是这么简单,有手就行。

具体把驱动文件chrdevbase.ko放到/lib/modules/4.1.15-gb8ddbbc目录中,就是放到这个一种库里;
应用程序chrdevApp放到home目录下或者其他正常的位置都行。

4、验证操作

insmod chrdevbase.ko	//加载驱动模块
cat /proc/devices		//查找能用的设备号
mknod /dev/chrdevbase c 200 0	//创建节点 “ 200”是设备的主设备号,“ 0”是设备的次设备号。

//操作这个设备节点
./chrdevbaseApp /dev/chrdevbase 1	
./chrdevbaseApp /dev/chrdevbase 2

rmmod chrdevbase.ko		//卸载驱动模块
lsmod	//查看某些模块还存不存在,

总结

这种简单的驱动模块,整体就传到板子上驱动文件.ko和应用程序文件。
驱动文件放到/lib下的库文件中,一直放着就行;
使用insmod命令加载,就是跟内核申请了一块儿空间;
使用mknod命令创建节点再/dev下,就是跟内核申请营业许可证;
APP可以放到任何正常的目录下,使用APP应用程序控制/dev下这个节点,就能实现驱动的控制了;
使用rmmod命令卸载,就是跟内核说一声退还这一块儿空间,APP就控制不了了。



遇到的大问题:
在这里插入图片描述
在加载驱动模块的时候出现的这个问题,原因是板子上的内核版本和编译驱动时候用到的内核不匹配。

解决办法: 强行将编译驱动的内核相关版本信息改成一样的,哪里不对改哪里。
内核顶层的Makefile中:

VERSION = 4
PATCHLEVEL = 1
SUBLEVEL = 15
EXTRAVERSION = -gb8ddbbc
NAME = Series 4800

.config文件和arch/arm/configs/imx_alientek_emmc_config文件:
CPU Core family selection,CPU内核全家桶中只留下----v7=y

然后再重新编译驱动:使用modinfo xxx.ko查看模块版本信息就和板子内核版本信息一样啦。

非常感谢原子论坛,好多问题都是从那上边儿找到的。



受限制的 HTML

  • 允许的HTML标签:<a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd> <h2 id> <h3 id> <h4 id> <h5 id> <h6 id>
  • 自动断行和分段。
  • 网页和电子邮件地址自动转换为链接。

相关推荐
  • 嵌入式Linux--linux字符设备驱动开发
    目录 一、字符设备驱动框架二、驱动模块的加载和卸载编写驱动的时候的注意事项 三、字符设备的注册于注销四、设备号五、file_operations的具体实现六、驱动代码工程七、Linux基本应用程序开发 一、字符设备驱动框架 字符设备驱动的编写主要就是驱动对应的open、close、read…其实就是file_operations结构体的成员变量的实现。  应用程序运行在用户空间,而 Linux 驱动属于内核的一部分,因此驱动运行于内核空间。当我们在用户空间想要实现对内核的操作,比如使用 open 函数打开/dev/led 这个驱动,因为用户空间不能直接对内核进行操作,因此必须使用一个叫做“系统调用”的方法来实现从用户空间“陷入”到内核空间,这样才能实现对底层驱动的操作。open、close、write 和 read 等这些函数是由 C 库提供的,在 Linux 系统中,系统调用作为 C 库的一部分。当我们调用 open 函数的时候流程如下图:  程序员重点关注的是应用程序和具体的驱动,应用程序使用到的函数在具体驱动程序中都有与之对应的函数,比如应用程序中调用了 open 这个函数,那么在驱动程序中也得有一个名为 open 的函数。每一个系统调用,在驱动中都有与之对应的一个驱动函数,在 Linux 内核文件 include/linux/fs.h 中有个叫做 file
  • 【超详细】嵌入式软件学习大纲
    目录 随便聊聊什么是嵌入式?我对嵌入式的看法单片机系统与嵌入式系统的差别PC系统与嵌入式系统的差别 单片机与嵌入式在学习模式上的差别主流的芯片平台开发模式的差别编程语言的差别软硬件组成的差别 嵌入式到底适不适合你学?嵌入式学习与编程语言和英语水平的差别哪些专业的朋友更适合嵌入式学习 嵌入式学习路线一、预备知识(基础学习)预备知识第一部分-linux基础学习大纲预备知识第一部分-C基础学习大纲 二、ARM裸机学习ARM琐碎知识的学习(至关重要)ARM裸机第二部分-ARM体系结构与汇编指令ARM裸机第三部分-开发板、原理图和数据手册ARM裸机第四部分-GPIO和LEDARM裸机第五部分-SDRAM和重定位relocateARM裸机第六部分-S5PV210的时钟系统ARM裸机第七部分-串口通信详解ARM裸机第八部分-按键和CPU的中断系统ARM裸机第九部分-定时器、看门狗和RTCARM裸机第十部分-SD卡启动详解ARM裸机第十一部分-NandFlash和iNandARM裸机第十二部分-I2C通信详解ARM裸机第十三部分-ADCARM裸机第十四部分-LCD显示器ARM裸机第十五部分-触摸屏TouchScreenARM裸机第十六部分-shell原理和问答机制引入 二、C语言进阶内存这个大话题C语言位操作指针才是C的精髓C语言复杂表达式与指针高级应用数组&字符串&结构体&共用体
  • 小白入手嵌入式的一点入门建议
    **我的嵌入式学习历程** 写在前面: 关于嵌入式的学习,CSDN或者知乎上的所有有价值的回答几乎都大同小异。我的回答以我自己的亲身经历结合我师兄师姐的经验提炼,希望对有兴趣投身这一行的有所启示。 本人安徽普通一本自动化专业小白一枚,在校学习主修课程是电路,数电模电,plc自控,电力电子技术等,大三一开始就没有考研的打算 就开始琢磨着找工作。在师兄师姐以及一些老师推荐。看我这个基础挺适合走嵌入式这条道路的,于是大三到大四秋招前便开始学习嵌入式技术 。 今年校招进的南京一家通讯公司做嵌入式开发(10k *14)。 本答案有部分引用其他版主回答,欢迎大家在评论留言探讨,一起学习,共同进步。 下面讲的一些都是些知识点概括性东西,让你知道嵌入式这行主要用到哪些知识。 在自学嵌入式这条过程中,我真的首推b站。 我一直认为b站是个学习平台,而非一个视频平台。里面诸如韦东山 华清远见。正点原子野火很多的教育教学视频都是免费的。 跟着后面学能了解不少东西。 最后,牛客力扣还有看准网刷一些大公司的嵌入式开发笔试面试题,看看学长学姐的面经。校招都能进去不错的公司。 嵌入式的技能清单和升级线路 第一部分:Linux平台搭建与环境熟悉 了解linux系统;区分各种版本的Linux系统,以便于拓展 Linux视野。 1、Linux 简介; 2、Linux 系统的主要特点; 3、Linux 的组成; 4
  • 【超详细】嵌入式软件学习大纲
    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 本文链接: https://blog.csdn.net/qq_34981463/article/details/102775157 <!--一个博主专栏付费入口结束--> <link rel="stylesheet" href="https://csdnimg.cn/release/phoenix/template/css/ck_htmledit_views-d284373521.css"> <div id="content_views" class="markdown_views prism-atom-one-light"> <!-- flowchart 箭头图标 勿删 --> <svg xmlns="http://www.w3.org/2000/svg" style="display: none;"> <path stroke-linecap="round" d="M5,0 0,2.5 5,5z" id="raphael-marker-block" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0);"></path> </svg> <p></p><div class="toc"><h3><a name="t0"></a
  • 从单片机到ARM Linux驱动——Linux驱动入门篇
    大一到大二这段时间里学习过单片机的相关知识,对单片机有一定的认识和了解。如果要深究其原理可能还差了一些火候。知道如何编写程序来点量一个LED灯,改一改官方提供的例程来实现一些功能做一些小东西,对IIC、SPI底层的通信协议有一定的了解,但是学着学着逐渐觉得单片机我也就只能改改代码了(当然有的代码也不一定能改出来)。对于我这种以后不想从事单片机开发想搬砖的码农来说已经差不多了(仅仅是个人观点)。 在单片机开发中我们常常用到的是裸机,并没有用到操作系统(或者接触过ucos/rtos这种实时操作系统),但是嵌入式Linux开发就必须得在Linux系统中进行操作。我们需要熟悉Linux操作系统,知道Linux的常用命令、文件系统、Linux网络、多线程/多进程,同时要会用vi编辑器、gcc编译器、shell脚本和一些简单的makefile的编写,在这些的基础之上进行Linux驱动开发的学习就会如步青云。 往期推荐: 史上最全的Linux常用命令汇总(超全面!超详细!)收藏这一篇就够了! STM32通过PWM产生频率为20HZ占空比为50%方波,并通过单片机测量频率并显示 嵌入式Linux操作系统具有:开放源码、所需容量小(最小的安装大约需要2MB)、不需著作权费用、成熟与稳定(经历这些年的发展与使用)、良好的支持等特点。因此被广泛应用于移动电话、个人数码等产品中
  • Linux系统应用程序设计,非常好的资料
    给大家推荐一个很不错的Linux学习应用程序开发的文章,只需要关注公众号,后台回复 【Linux系统应用程序设计】即可获取。 目录如下:第 1 章 Linux 快速入门… 1 1.1 嵌入式 Linux 基础 … 1 1.1.1 Linux 发展概述… 1 1.1.2 Linux 作为嵌入式操作系统的优势 … 2 1.1.3 Linux 发行版本… 3 1.1.4 如何学习 Linux … 4 1.2 Linux 安装… 5 1.2.1 基础概念… 5 1.2.2 硬件需求… 7 1.2.3 安装准备… 7 1.2.4 安装过程… 8 1.3 Linux 文件及文件系统…11 1.3.1 文件类型及文件属性…11 1.3.2 文件系统类型介绍… 13 1.3.3 Linux 目录结构… 14 1.4 实验内容——安装 Linux 操作系统 … 17 本章小结… 17 思考与练习… 18 第 2 章 Linux 基础命令… 19 2.1 Linux 常用操作命令… 19 2.1.1 用户系统相关命令… 20 2.1.2 文件目录相关命令… 27 2.1.3 压缩打包相关命令… 38 2.1.4 比较合并文件相关命令… 40 2.1.5 网络相关命令… 45 A-PDF MERGER DEMO║2 嵌入式 应用程序开发详解 2.2 Linux 启动过程详解… 50 2.2.1 概述
  • 迅为IMX6ULL开发板Linux学习教程
    1800+页使用手册(持续更新)+入门视频教程+实战视频教程 关注VX公众号:迅为电子 , 回复 :终结者,免费获取产品资料 让教程更细致,终结入门难! 所有教程由迅为原创,是迅为工作多年的工程师精心编写,深知初学者痛点。从Linux入门到驱动开发再到系统移植,一步一步由浅入深,逐步掌握嵌入式技术。每个例子有详细的注释,每个命令详细的说明。只要你对嵌入式感兴趣,就可以学习。 注意事项与售后维修 2 技术支持与开发定制 4 资料获取与后续更新 5 更新记录 6 前言 开发板使用前必读 31 必须注意的问题 31 一 光盘资料介绍 31 二 开发板使用前装配流程 33 第一部分 总领及学习指引 37 01 框架学习法 38 02 嵌入式系统学习的框架 39 03 关于裸机程序和仿真 41 04 基于迅为开发板的学习步骤 42 第一章 开发板初体验 44 1.1 初识i.MX6ULL终结者开发板 45 1.2 i.MX6ULL终结者硬件资源说明 46 1.2.1 i.MX6ULL核心板资源说明 46 1.2.2 i.MX6ULL终结者底板资源说明 48 1.3 i.MX6ULL终结者底板原理分析 51 1.3.1 核心板接口 51 1.3.2 启动方式原理部分 52 1.3.3 系统电源接口 53 1.3.4 复位电路 54 1.3.5 纽扣电池电路 55 1.3.6 CAN接口电路
  • 嵌入式考纲
    嵌入式考纲 简答题:1-2微处理器,2-1-2ARM处理器和指令集内部结构2,4嵌入式软件编程技术 5开发环境和调试技术 7-1linux操作系统 8嵌入式文件系统 课后思考题 1.5.6.7 2.3.4.5.6.7.9.11.13 4 5 7.2.5.8.10 8 文章目录 嵌入式考纲第一章3.**嵌入式系统的定义**;4. **嵌入式系统与桌面通用系统的区别**;5. **嵌入式处理器的基本特征**;**6. 嵌入式处理器的种类及特点(MCU、DSP、MPU、SOC);****7. 典型嵌入式处理器的特点及应用场景(ARM、MIPS、POWERPC)****8.** **嵌入式软件系统的体系结构及各个层次的任务;****9.** **嵌入式操作系统的主要特性;****11.** **典型嵌入式操作系统的特点及应用场景**(**Linux** **、VXwork**、**QNX**、**uC/OS**)。 第二章**1.ARM** **微处理器的特点;**ARM7系列ARM9系列ARM9E系列ARM10E系列Xscale**3.CISC** 与 **RISC** **体系结构的特点及区别;****4.冯.诺依曼结构和哈佛结构的特点及区别****5.ARM** **微处理器的工作状态及区别;****6.ARM体系结构的存储器格式;****7.ARM** **处理器的** **7**
  • 《创客学院嵌入式从入门到精通》笔记--10全面掌握嵌入式系统移植
    目录 01嵌入式基本概念,嵌入式开发环境搭建,目标机搭建,TFTP服务搭建,NFS服务搭建 1.系统移植概述及环境搭建 1.通用嵌入式系统软件组成部分 2.Linux 在嵌入式中应用的条件与前景 3.嵌入式Linux内核结构​ 4.Android系统 2.嵌入式开发环境搭建 1.嵌入式Linux交叉开发环境硬件基本组成 2.开发板启动流程 3.搭建嵌入式Linux开发环境的主要工作 4.开发主机搭建 5.TFTP 服务 6.NFS 服务 7.目标机安装(u-boot烧写调试) – 需要SD卡 8.fastboot烧写 02Bootloader移植(bootloader基本概念,U-boot常用命令和配置编译 1.Bootloader 介绍 1.什么是Bootloader 2.Bootloader的特点 3.Bootloader的操作模式 2.常用bootloader介绍 3.U-boot介绍 4.U-boot命令介绍 1.printenv 显示所有环境变量 2.setenv 设置新的环境变量 3.saveenv 将当前定义的所有的环境变量值存入flash中 4.tftp 通过网络下载程序 5.protect 对Nor Flash写保护 6.erase 擦除Nor FLASH 7.Nand相关命令 8.movi 命令 9.bootcmd 自启动命令 10.bootm kernel
  • 【Android底层学习总结】1. 驱动开发基础
    0 目录 1 前言2 驱动开发认识2.1 驱动2.1.1 设备驱动程序的主要功能2.1.2 驱动程序的主要类型2.1.3 设备文件2.1.4 sys文件系统: 3 基础编程3.1 内核模块3.1.1 设备驱动的编译和加载方式3.1.2 一个模块被插入时的主要工作 3.2 内核编程3.2.1 内核模块编程模板3.3 字符驱动程序模板 4 总结 1 前言 已经有段时间没好好地写博客了,最近在研究安卓底层,所以想写写我对安卓底层的认识和总结。本篇是安卓底层学习总结系列的第一篇,驱动开发基础。 2 驱动开发认识 安卓系统,想必我也不用作太多介绍,这里我要提及的是安卓系统和嵌入式系统十分接近,所编写的驱动程序实际上大多也可以认为是嵌入式驱动程序。并且安卓的内核是Linux,所以写安卓驱动程序实际上和写Linux内核模块差不多,我门这篇主要认识PC中的Linux驱动。 2.1 驱动 所谓驱动,就是内核与外部设备的媒介,下面介绍有关驱动需要知道的知识。 2.1.1 设备驱动程序的主要功能 对设备初始化和释放内核与硬件的数据交互应用程序和硬件的数据交互硬件的错误检测 2.1.2 驱动程序的主要类型 字符设备 – 使用自己制定的数据大小,通常以字节为单位输入输出块设备 – 以块为单位输入输出 – 对块设备读写时,利用系统内存作缓冲区,当用户进程对设备请求能满足用户的要求就返回请求的数据网络设备 2
  • 一个普通应届生的2020秋招总结(文末送福利)
    秋招是每个在校学生都要经历的一个阶段。本篇文章记录了自己的秋招历程。秋招投递公司23家,简历被刷1家。笔试/测评挂掉3家。至今无消息的8家。获得Offer的公司有小米,兆易创新,全志科技,浙江大华,海格通信,京信通信,景嘉微电子,广州朗国电子,北京华大电子,中国长城科技集团。已签约浙江大华。最后收获了一个满意的Offer。前事不忘,后事之师。希望自己总结的这些内容能对后面准备秋招的同学有所帮助!1. 自我介绍  本硕双非,本科电子信息工程,硕士电子与通信工程。导师申请的项目中有一部分需要用Stm32实现,所以自己在硕士期间接触Stm32比较多。当时也考虑到,如果只会Stm32,找工作可能会比较吃力。而自己对嵌入式底层的内容也比较感兴趣。所以,在研二的时候每天花一点时间来学习下驱动开发,以后找工作打算从事底层驱动开发相关的内容。2. 秋招准备2.1 Linux驱动  在2019年12月的时候,基本就把韦东山老师的第二期课程学习了一遍了,虽然在学习过程中有很多不明白的,但也坚持看了一遍。把有疑问的地方记录了下来,打算后面再慢慢的去深入研究。  韦东山老师讲的课程确实很好,但是对于基础不太好的可能会比较吃力,很容易劝退。当时思考了下,自己为什么听不懂呢,哪里有欠缺?我们对自己应该有一个清晰的认识,我从Stm32转驱动开发,优势就是我对于基本的硬件原理都比较熟悉
  • 嵌入式linux驱动开发教程
    目录 第二章 内核模块宏内核和微内核内核模块程序的初始化和退出函数原型内核模块的相关工具内核模块基本框架(内核最原始的结构)多个源文件编译生成一个内核模块内核模块参数(参数类型要注意)内核模块依赖内核模块和普通应用程序之间的差异(简答题) 第三章 字符设备驱动设备驱动的种类不同设备驱动的特点字符设备驱动基础主设备号和次设备号 字符设备驱动框架(编程题)虚拟串口设备操作一个驱动支持多个设备 第四章 高级I/O操作ioctl 设备操作proc 文件操作非阻塞型I/O阻塞型I/OI/O多路复用异步I/O对四种I/O模型的总结(问答题)异步通知mmap 设备文件操作 编程题 第二章 内核模块 宏内核和微内核 宏内核和微内核:linux是宏内核的代表,Windows是微内核的代表。区别是宏内核所有的内核功能被整体编译在一起,形成一个单独的内核镜像文件。 内核模块:linux引入了内核模块。内核模块是单独编译的一段内核代码,在需要的时候动态加载,不需要的时候动态卸载,动态增加内核功能。内核模块有助于减小内核镜像文件的体积,减少内核占用的内存空间。(当然内核模块不一定都是驱动程序,驱动程序也不一定都是内核模块) 内核模块程序的初始化和退出函数原型 // 一个模块程序几乎都要直接或间接使用这三个头文件 #include <linux/init.h> #include <linux/kernel.h
  • 嵌入式linux开发uboot移植(六)——uboot环境变量
    嵌入式linux开发uboot移植(六)——uboot环境变量一、uboot环境变量简介 u-boot的缺省情况下会有一些基本的环境变量,当执行saveenv时,环境变量会保存到flash存储设备中。如果环境变量的值为空,则uboot会使用uboot代码中的值;如果环境变量不为空,则优先使用环境变量的值。默认环境变量在uboot源码中common/Env_common.c文件中。 uchar default_environment[CFG_ENV_SIZE] = {#ifdef CONFIG_BOOTARGS"bootargs="CONFIG_BOOTARGS"\0"#endif#ifde fCONFIG_BOOTCOMMAND"bootcmd="CONFIG_BOOTCOMMAND"\0"#endif#ifdef CONFIG_MTDPARTITION"mtdpart="CONFIG_MTDPARTITION"\0"#endif#ifdef CONFIG_RAMBOOTCOMMAND"ramboot="CONFIG_RAMBOOTCOMMAND"\0"#endif#ifdef CONFIG_NFSBOOTCOMMAND"nfsboot="CONFIG_NFSBOOTCOMMAND"\0"#endif#if defined(CONFIG_BOOTDELAY) && (CONFIG
  • 嵌入式Linux | 从单片机工程师的角度看嵌入式Linux
    1024G 嵌入式资源大放送!包括但不限于C/C++、单片机、Linux等。关注微信公众号【嵌入式大杂烩】,回复1024,即可免费获取! 前言 这篇文章简单我们来一起梳理嵌入式Linux的一些知识,方便于一些想跟我一样想要由单片机进阶到嵌入式Linux的朋友做一些参考学习。 嵌入式Linux学哪些东西 1、认识Linux 学单片机的朋友有些有一些Linux基础了,但也不乏有些朋友没用过Linux,甚至有些初学的读者朋友没听说过Linux,为了照顾这些朋友,这里简单地认识一下Linux: 2、认识嵌入式Linux 学习嵌入式Linux我们需要关注以下几大块内容: 嵌入式Linux软件部分最重要的三部分当属Bootloader、Linux内核、根文件系统。有了这三部分,这才是一个最小的、完整的、可运行程序的嵌入式系统。 (1)BootLoader BootLoader 是在操作系统运行之前运行的一段代码, 用于引导操作系统。开源的BootLoader 有很多种,比如RedBoot、U-Boot 等 ,其中U-Boot用得最多。U-Boot的源码非常庞大: 我们的学习重心在于学会怎么把芯片原厂移植好的U-Boot拿来修改然后适配我们的板子: 平时说的U-Boot移植通常都是指把芯片原厂移植好的U-Boot拿来修改适配我们板子的过程。 (2)Linux内核 Linux 内核(英语:
  • 嵌入式驱动工程师开发学习路线
    ARM+LINUX路线,主攻嵌入式Linux操作系统及其上应用软件开发目标: (1)掌握主流嵌入式微处理器的结构与原理(初步定为arm9) (2)必须掌握一个嵌入式操作系统 (初步定为uclinux或linux,版本待定) (3)必须熟悉嵌入式软件开发流程并至少做一个嵌入式软件项目。 从事嵌入式软件开发的缺点是: (1) 入门起点较高,所用到的技术往往都有一定难度,若软硬件基础不好,特别是操作系统级软件功底不深,则可能不适于此行。 (2)这方面的企业数量要远少于企业计算类企业。 (3)有少数公司经常要硕士以上的人搞嵌入式,主要是基于嵌入式的难度。但大多数公司也并无此要求,只要有经验即可。 (4)平台依托强,换平台比较辛苦。 方法步骤: 1、基础知识: 目的:能看懂硬件工作原理,但重点在嵌入式软件,特别是操作系统级软件,那将是我的优势。 科目:数字电路、计算机组成原理、嵌入式微处理器结构。 汇编语言、C/C++、编译原理、离散数学。 数据结构和算法、操作系统、软件工程、网络、数据库。 方法:虽科目众多,但都是较简单的基础。不一定全学,可根据需要选修。 主攻书籍:the c++ programming language(一直没时间读)、数据结构-C2。 2、学习linux: 目的:深入掌握linux系统。 方法:使用linux—〉linxu系统编程开发—〉驱动开发和分析linux内核
  • 嵌入式linux开发lcd设备驱动的学习
    嵌入式系统中使用的linux OS的话,lcd的开发主要内容就是LCD设备驱动的移植,使用芯片SDK中现有的LCD框架做修改,在linux版本3.10之前没有使用dts和这之后使用的dts,还是有些区别的。主要修改调试的地方如:平台设备文件或者是dts节点内容,这需要根据具体使用的操作系统而定。以完成对LCD的驱动工作。 但是在这起之前还是有很多的基本概念需要清楚。 以下来自网络。 FrameBuffer 的原理:在linux系统中中LCD这类设备称为帧缓冲设备,英文frameBuffer设备。 用户可以将Framebuffer看成是显示内存的一个映像,将其映射到进程地址空间之后,就可以直接进行读写操作,而写操作可以立即反应在屏幕上。这种操作是抽象的,统一的。用户不必关心物理显存的位置、换页机制等等具体细节。这些都是由Framebuffer设备驱动来完成的。 framebuffer的设备文件一般是 /dev/fb0、/dev/fb1 等,framebuffer最多支持32个设备。framebuffer是个字符设备,主设备号为29,对应于/dev/fb%d设备文件。通常,使用如下方式(前面的数字表示次设备号)0 = /dev/fb0 第一个fb 设备1 = /dev/fb1 第二个fb 设备。 fb 也是一种普通的内存设备,可以读写其内容。例如,屏幕抓屏:cp /dev/fb0
  • 2020秋招联发科小米等面经分享
    秋招投递公司23家,简历被刷1家。笔试/测评挂掉3家。至今无消息的8家。获得Offer的公司有小米,兆易创新,全志科技,浙江大华,海格通信,京信通信,景嘉微电子,广州朗国电子,北京华大电子,中国长科技集团。已签约浙江大华。有面试联发科北京(7.16)广州朗国电子科技(8.24)浙江大华股份(9.3)兆易创新(9.3)景嘉微(9.13)全志科技(9.15)小米(9.15)中国长城科技集团(9.15)CEC子公司-北京华大电子(9.15)京信通信(9.16)海格通信(9.27)简历被刷oppo(8.23)笔试/测评挂海康威视(9.1)乐鑫(8.18)CVTE(9.16)没消息寒武纪(9.3)华为(9.10)BOE(8.25 & 9.12)恩智浦(9.15)瑞芯微(9.17)紫光展锐(9.18)联发科成都(9.18)小马智行(9.18)总结  友情提示:公司名字后面的日期代表投递日期,面试批次后面的时间代表面试时长和面试日期。有面试联发科北京(7.16)  20200805接到通知,0806早上九点半面试。邮件中写的是用Webex Meet,之前都没听过的一个软件,网上找了半天才找到,而且软件没有简体,只好调成繁体了。邮件中写的是等待通知后再连入,大概9.40的时候接到了电话,要我加入会议中。面试官是个女的,首先让我自我介绍下,然后开始看我的简历。介绍完了直接问项目。一面(35min,8
  • 树莓派开发
    文章目录 树莓派树莓派开发概述什么是嵌入式ARM架构 登录到树莓派树莓派刷机树莓派登录其他操作 Linux库分文件编程函数库静态库的制作动态库的制作 外设开发编程树莓派接口wiringPi库串口通信 交叉编译与链接交叉编译是什么为什么怎么做 软链接和硬链接概念生成 ubuntu上编译带wiringPi库的交叉编译方式1方式2 树莓派高阶开发概述芯片带操作系统的启动树莓派Linux源码目录树分析树莓派Linux源码配置概述配置 树莓派Linux内核编译编译打包挂载SD卡数据拷贝 文件系统和驱动文件系统概述虚拟文件系统(VFS) Linux内核框架驱动程序概述编写简单字符设备驱动编译和测试驱动代码 地址和I/O操控地址树莓派BCM2835 ARM芯片手册按位运算I/O操控代码操控代码调试和测试 树莓派 树莓派开发 概述 什么是嵌入式 嵌入式即嵌入式系统,以应用为中心,以计算机技术为基础,软硬件可剪裁,适应应用系统对功能、可靠性、成本、体积、功耗等严格要求的专用计算机系统 嵌入式芯片:单片机如共享单车的锁 低端单片机搞不定,用ARM架构,如STM32,比如ARM+Linux+QT,比如安卓系统 QT方案:通常基于Linux 安卓方案:基于安卓,高通,华为海思等 ARM架构 英国ARM公司(中国区总部在上海) 硬件架构的一种:ARM结构 stm32,高通,树莓派等 ​ Intel架构
  • 树莓派驱动开发编写调试(1)
    目录 驱动的认知用户内核硬件 如何添加驱动基于框架编写驱动代码设备驱动框架测试代码配置过程 驱动的认知 Linux一切皆文件:鼠标,键盘,led,flash内存,网卡都是文件 登录树莓哌可以查看:在/dev下。 那么open是如何区分我打开的到底是什么呢?需要驱动吗? 树莓派有很多引脚,都有驱动。比如引脚4驱动,这都需要我们实现,在内核源码添加,比如fd = open ("/dev/pin",权限),有俩部分:文件名(基于驱动框架不需要文件名)和设备号,用ls -l查看。 主设备号和次设备号区别: Linux的设备管理是和文件系统紧密结合的,各种设备都以文件的形式存放在/dev目录下,称为设备文件。应用程序可以打开、关闭和读写这些设备文件,完成对设备的操作,就像操作普通的数据文件一样。为了管理这些设备,系统为设备编了号,每个设备号又分为主设备号和次设备号。主设备号用来区分不同种类的设备,而次设备号用来区分同一类型的多个设备。对于常用设备,Linux有约定俗成的编号,如硬盘的主设备号是3。 一个字符设备或者块设备都有一个主设备号和次设备号。主设备号和次设备号统称为设备号。主设备号用来表示一个特定的驱动程序。次设备号用来表示使用该驱动程序的各设备。例如一个嵌入式系统,有两个LED指示灯,LED灯需要独立的打开或者关闭。那么,可以写一个LED灯的字符设备驱动程序
  • 痞子衡嵌入式:恩智浦SDK驱动代码风格检查工具预览版
    过去的三天里我花了一些时间做了一个基于 PyQt5 的 GUI 工具,可以帮助检查你的代码风格是否符合恩智浦 SDK 驱动开发规范,如今这个工具的第一个预览版(v0.4)出来了大家好,我是痞子衡,是正经搞技术的痞子。  接上文 《恩智浦SDK驱动代码风格、模板、检查工具》 继续聊,是的,过去的三天里我花了一些时间做了一个基于 PyQt5 的 GUI 工具,可以帮助检查你的代码风格是否符合恩智浦 SDK 驱动开发规范,如今这个工具的第一个预览版(v0.4)出来了,欢迎大家试用(当然更欢迎加入这个开源项目一起来开发)。  我知道你不是恩智浦 SDK 驱动的开发者,但恩智浦毕竟是一线 MCU 大厂,作为一个嵌入式从业者(尤其是你还没有找到一个明确的代码风格),如果写的代码能符合恩智浦规范,何尝不是一件快事!1.代码风格风格细则:https://github.com/JayHeng/MCUX-SDK-Coding-Style/blob/master/coding_style.md2.代码模板头文件模板:https://github.com/JayHeng/MCUX-SDK-Coding-Style/blob/master/template.h源文件模板:https://github.com/JayHeng/MCUX-SDK-Coding-Style/blob/master/template