鸿蒙移植i.mx6ull(五) 移植概述
文章目录
- 1.框架
- 2. 嵌入式软件系统的组成
- 3. 移植最小系统要做的几件事
- 4. 想做更多
- 5. 基础知识
-
- 5.1 单片机相关的知识
- 5.2 Linux操作相关的知识
- 5.3 芯片相关知识
- 6. 驱动程序知识
-
- 6.1 以点灯为例
-
- 6.1.1 硬件原理
- 6.1.2 单片机点灯
- 6.1.3 Liteos-a/Linux怎么点灯
- 6.2 怎么编写驱动程序
-
- 6.2.1 驱动程序的核心
-
- 1. Linux
- 2. Liteos-a
- 6.3 注册驱动程序
-
- 1. Linux
- 2. Liteos-a
- 6.4 APP如何使用
1.框架
鸿蒙是一套完整的、普通人可以直接使用的操作系统,跟
Windows
、安卓、IOS
类似。
常见的错误观点是把鸿蒙跟Linux
放在一起来对比,这不对:
Linux
只是一个内核,普通人无法使用还需要在
Linux
之上安装各类程序比如
Ubuntu
等发行版,它们在Linux
内核之上,还有桌面、各类办公软件鸿蒙支持多种内核:
Linux
、Liteos
(又分为Liteos-a
、Liteos-m
)在内核之上,鸿蒙还有各种子系统,在子系统之上又有桌面等软件
2. 嵌入式软件系统的组成
我们可以把内核之上的软件,简单称为APP(实际上还可以细分,比如各类子系统、桌面等APP)。
启动内核,并不复杂,使用U-boot即可。
3. 移植最小系统要做的几件事
串口相关
- 打印(只是打印调试信息)
- 串口驱动(可发可收,
APP
执行printf
时可以从串口打印,所以需要驱动)
MMU
(Memory Management Unit
,内存管理单元)的设置:虚拟地址与物理地址
完善中断子系统
- 提供系统tick时钟
- 为串口驱动实现基于中断的读取字符函数
- 实现存储设备驱动程序
- 在存储设备上烧录文件系统
3.1 串口相关
与
Linux
的串口驱动相比,鸿蒙的串口驱动极大简化了。
对于输出:不使用中断,直接使用查询方式输出。
对于输入:使用中断,我们只需要提供底层硬件相关的代码。
要注意:使用的是虚拟地址。
3.2 MMU设置
MMU有2大功能:
3.2.1 权限管理
- 比如可以把进程A、B的地址空间完全隔离开,它们互不影响
- 写得差的进程、有恶意的进程,不能影响到其他进程
- 用户程序、内核地址空间完全隔离开:不允许用户直接访问硬件
3.2.2 地址映射
使能MMU后,CPU发出的地址被称为"虚拟地址",它不是直接发送给硬件,而是发给MMU
MMU根据页表
- 进行权限判定
- 转换为物理地址,发给外设
运行app1时,CPU发出的addr,通过MMU映射到paddr1;
运行app2时,CPU发出的同一个addr,通过MMU映射到paddr2;
虽然app1、app2使用的地址相同,但是对应的内存不同在移植过程中,我们不需要关注“权限”,只需要关注“地址映射”
3.3 中断子系统
操作系统跟单片机程序最大的区别,就是多任务,也就是同时运行多个程序。
同时,对人类来说是这样的,多个程序好像可以同时运行,实际上它们是轮流运行。
3.3.1 操作系统"同时"运行多个任务
轮流运行:
3.3.2 串口接收数据的中断
使用串口接收数据时,如果使用"查询"方式,低效并且费电。 一般都使用中断方式。
3.4 存储设备的驱动程序
板子上一般都有
EMMC
、SD/TF
卡、Nor Flash
、Nand Flash
等存储设备。Nor Flash
、Nand Flash
的驱动程序相对简单,但是这些设备比较少见了。 而EMMC
、SD/TF
卡的驱动程序又太复杂,足够出一个专题了。
我们聚焦在最小系统的移植,先把流程走通:用内存来模拟Flash
。
3.5 根文件系统
光有存储设备还不行,上面需要有文件:这就是根文件系统。
一个程序要能运行,出了你写出的程序本身,还需要其他库,比如printf
就不是你写的,它在库文件里。 根文件系统里会有这些内容:
- 程序
- 库
- 配置文件
- 用户数据(可选)
- 驱动程序(可选)
4. 想做更多
- 为有更好的人机交互可以移植
LCD
、触摸屏驱动- 为了方便开发,移植
EMMC
驱动、网卡驱动- 要接各类外设,还需要
I2C
、SPI
、GPIO
、UART
驱动- 摄像头、声卡驱动
5. 基础知识
移植内核对技术的要求比较全面、比较细致。
5.1 单片机相关的知识
栈的作用
加载地址、链接地址
重定位
几个简单的硬件知识 * 串口 * 定时器
中断的概念
5.2 Linux操作相关的知识
Linux
常用命令- 简单的脚本:脚本就是把命令写在一个文件里
GCC
编译命令Kconfig
和Makefile
5.3 芯片相关知识
- 能阅读芯片手册(英文)
- 移植最小系统时,涉及的手册内容不多
- 能看懂硬件原理图
- 移植最小系统时,涉及的原理图内容不多
6. 驱动程序知识
对于只有单片机知识的人来说,怎么去操作硬件?
- 直接读写寄存器
- 使用库函数
在
RTOS
中,本质也是去读写寄存器,但是需要有统一的驱动程序框架。 所以:RTOS驱动 = 驱动框架 + 硬件操作
6.1 以点灯为例
6.1.1 硬件原理
6.1.2 单片机点灯
- 方法1:直接读写寄存器
- 方法2:使用厂家的HAL库
6.1.3 Liteos-a/Linux怎么点灯
使用
MMU
时,一般APP
与内核是相互隔离的。APP
通过标准的open/read/write
等文件操作函数去调用驱动程序。
如下图所示:
为何要多此一举?
- 它们支持MMU(内存管理单元)
- 用户程序跟内核是分隔开的,用户程序不能直接读写寄存器
- 用户程序通过标准接口访问驱动程序
- 基于这些内核的软件一般都比单片机软件复杂,术业有专攻
- 不应该让写APP的人去看原理图、写驱动、写寄存器
- 软件和硬件隔离,硬件再怎么变化,只需要改驱动,APP不需要改
6.2 怎么编写驱动程序
6.2.1 驱动程序的核心
Linux
和Liteos-a
的驱动程序时类似的,Liteos-a
的更加精简。
既然APP
使用驱动是调用open/read/write
等接口,那么写驱动程序是最简单的方法就是提供对应的drv_open/drv_read/drv_write
等函数。
这些函数放在一个结构体里:Linux
对应file_operations
结构体,Liteos-a
对应file_operations_vfs
结构体。
1. Linux
Linux
中是定义一个file_operations
结构体,如下:
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 (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);int (*readdir) (struct file *, void *, filldir_t);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 (*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);int (*lock) (struct file *, int, struct file_lock *);ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);int (*check_flags)(int);int (*flock) (struct file *, int, struct file_lock *);ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);int (*setlease)(struct file *, long, struct file_lock **);long (*fallocate)(struct file *file, int mode, loff_t offset, loff_t len);int (*show_fdinfo)(struct seq_file *m, struct file *f);};
2. Liteos-a
Liteos-a
中定义了一个file_operations_vfs
结构体,如下:
struct file_operations_vfs{ /* The device driver open method differs from the mountpoint open method */ int (*open)(FAR struct file *filep); /* The following methods must be identical in signature and position because * the struct file_operations and struct mountp_operations are treated like * unions. */ int (*close)(FAR struct file *filep); ssize_t (*read)(FAR struct file *filep, FAR char *buffer, size_t buflen); ssize_t (*write)(FAR struct file *filep, FAR const char *buffer, size_t buflen); off_t (*seek)(FAR struct file *filep, off_t offset, int whence); int (*ioctl)(FAR struct file *filep, int cmd, unsigned long arg); int (*mmap)(FAR struct file* filep, struct VmMapRegion *region); /* The two structures need not be common after this point */#ifndef CONFIG_DISABLE_POLL int (*poll)(FAR struct file *filep, poll_table *fds);#endif int (*unlink)(FAR struct inode *inode);};
6.3 注册驱动程序
1. Linux
static struct file_operations hello_drv = {.owner = THIS_MODULE,.open = hello_drv_open,.read = hello_drv_read,.write = hello_drv_write,.release = hello_drv_close,};int major = register_chrdev(0, "hello", &hello_drv); /* /dev/hello */static struct class *hello_class = class_create(THIS_MODULE, "hello_class");device_create(hello_class, NULL, MKDEV(major, 0), NULL, "hello"); /* /dev/hello */
2. Liteos-a
static const struct file_operations_vfs g_helloDevOps = { .open = hello_open, .close = hello_close, .read = hello_read, .write = NULL, .seek = NULL, .ioctl = NULL, .mmap = NULL, .unlink = NULL,};int ret = register_driver("/dev/hello", &g_helloDevOps, 0666, NULL);
6.4 APP如何使用
Linux
和Liteos-a
在APP层面都一样:
int main(int argc, char **argv){int fd;char buf[1024];int len;/* 1. 判断参数 */if (argc < 2) {printf("Usage: %s -w \n", argv[0]);printf("%s -r\n", argv[0]);return -1;}/* 2. 打开文件 */fd = open("/dev/hello", O_RDWR);if (fd == -1){printf("can not open file /dev/hello\n");return -1;}/* 3. 写文件或读文件 */if ((0 == strcmp(argv[1], "-w")) && (argc == 3)){len = strlen(argv[2]) + 1;len = len < 1024 ? len : 1024;write(fd, argv[2], len);}else{len = read(fd, buf, 1024);buf[1023] = '\0';printf("APP read : %s\n", buf);}close(fd);return 0;}