文章目录
Ps:这个鸿蒙系列是韦东山老师录制的视频和开发手册为基础,请大家支持韦老师。 这个专栏是:
1.学习的笔记记录。
2.整理和知识点汇总。
3.个人做的项目经验汇总。
1. Liteos-a中串口的使用
1.1 内核里打印
内核打印函数是
PRINT_RELEASE
,它的内部调用关系如下:
PRINT_RELEASE
LOS_LkPrint
g_osLkHook
OsLkDefaultFunc
OsVprintf
UartPuts
UartPutsReg
UartPutStr
UartPutcReg
我们要实现
UartPutcReg
,用来输出单个字符。
1.2 APP控制台
我们编写的应用程序,调用
printf
时,那些信息从哪里打印出来?从控制台。 在串口上运行程序,控制台就是串口。
远程登录板子后运行程序,控制台就是远程登录终端。控制台的实现分为4层:
1.2.1 /dev/console
init
进程打开的就是/dev/console
,它会打开shell
。
我们在shell
里执行各种APP
时,这些APP
会继承父进程的3
个设备:标准输入、标准输出、标准错误,都对应/dev/console
。
我们编写的APP
,一般不需要自己去打开/dev/console
,它已经继承得到了。
在串口上运行程序,/dev/console
就是串口。 远程登录板子后运行程序,/dev/console
就是远程登录终端。
所以/dev/console
表示的是当前终端
,它可能对应不同的设备,比如/dev/serial
或/dev/telnet
。
1.2.2 /dev/serial
在
Liteos-a
中,/dev/serial
被称为virtual serial,虚拟串口。它只是起一个中转的作用,无论是APP还是内核,使用/dev/serial
时,都是再次跳转去执行具体串口设备驱动程序的函数。比如:
那么,
/dev/serial
这个虚拟串口,怎么跟具体串口挂钩?也就是上图中,GetFileOps
函数为何能得到具体串口的驱动程序?
方法如下图所示:
virtual_serial_init
函数会找到/dev/uartdev-0
的驱动程序(即它对应的struct inode
,里面含有file_operations_vfs
)。
1.2.3 /dev/uartddev-0
1. 总体介绍
这是真正操作硬件的驱动程序,它分为两部分:
device_t
、driver_t
。
- 在
device_t
中设置资源,比如寄存器物理基地址、中断号等- 在
driver_t
中提供函数,比如device_probe
、device_attach
函数- 当内核发现有名字系统的
device_t
、driver_t
时- 就会调用
driver_t
中的device_probe
、device_attach
函数- 在里面根据
device_t
得到资源、注册驱动register_driver
这种写驱动程序的方法,被称为分离:操作函数、资源分离。
以后想换一个硬件,只需要修改device_t
就可以,driver_t
保存不变。
2. device_t
示例代码:
3. drvier_t
先注册一个
drvier_t
结构体,它里面带有各类device_method_t
:
当内核发现有同名的
device_t
和driver_t
时,就会调用driver_t
里面提供的device_probe
、device_attach
函数。
4. uartdev_fops
在
device_attach
函数里从device_t
里获取硬件资源、注册驱动:
/dev/uartdev-0
对应的驱动程序时uartdev_fops
,它通过uart_ops
来操作硬件。
1.2.4 uart_ops
在
UART
驱动程序里,我们只需要提供硬件操作部分:
uart_ops
里有4个函数:
config
:配置串口,比如波特率等startup
:启动串口,比如注册中断处理函数、启动串口start_tx
:发送字符串shutdown
:关闭串口
串口就两大功能:发送数据、接收数据。
在Liteos-a
中,发送数据比较简单:没有使用中断,而是使用查询方式逐个发送,核心是UartPutcReg
。
接收数据时使用中断,所以需要注册串口接收中断处理函数,它要做的事情是:
- 发生中断时,读取硬件获得字符,可能有多个字符
- 处理特殊字符:比如不`\r`换为`\n`
- 通知上层代码:
udd->recv(udd, buf, count);
2. 串口移植
我们的目标是:让最小系统启动。 那么对于串口,不需要考虑得很全面:
- 不需要初始化串口:
u-boot
已经初始化串口了- 不需要动态配置串口:固定使用某个波特率等配置就可以(在
u-boot
里设置过了)移植工作只需要实现这几点:
串口发送单个字符
注册串口接收中断函数:确定中断号、使能中断、在中断函数中读取数据
2.1 最终结果
本章节做的修改会制作为补丁文件:
03_openharmony_uart_imx6ull.patch
假设目录
openharmony
中是未修改的代码,从没打过补丁;
假设补丁文件放在openharmony
的同级目录; 打补丁方法如下:
$ cd openharmony
$ patch -p1 < ../openharmony_100ask_v1.2.patch
$ patch -p1 < ../01_openharmony_add_demo_board.patch
$ patch -p1 < ../02_openharmony_memmap_imx6ull.patch
$ patch -p1 < ../03_openharmony_uart_imx6ull.patch
打上补丁后,可以如此编译:
$ cd kernel/liteos_a
$ cp tools/build/config/debug/demochip_clang.config .config
$ make clean
$ make
2.2 串口发送单个字符
2.3 在device_t中指定资源
需要确定2个资源:寄存器地址、中断号
2.4 实现uart_ops
在
UART
驱动程序里,uart_ops
结构体封装了UART的硬件操作:
uart_ops
里有4个函数:
config
:配置串口,比如波特率等startup
:启动串口,比如注册中断处理函数、启动串口start_tx
:发送字符串shutdown
:关闭串口我们只需要实现
startup
、start_tx
,其他函数可以设为空:
startup
:确定中断号、request_irq
、使能中断、提供中断处理函数start_tx
:发送字符串
2.5 GIC
在
kernel\liteos_a\platform\main.c
中,调用OsSystemInfo
打印系统信息时,代码如下:
PRINT_RELEASE("\n******************Welcome******************\n\n"
"Processor : %s"
#if (LOSCFG_KERNEL_SMP == YES)
" * %d\n"
"Run Mode : SMP\n"
#else
"\n"
"Run Mode : UP\n"
#endif
"GIC Rev : %s\n"
"build time : %s %s\n"
"Kernel : %s %d.%d.%d.%d/%s\n"
"\n*******************************************\n",
LOS_CpuInfo(),
#if (LOSCFG_KERNEL_SMP == YES)
LOSCFG_KERNEL_SMP_CORE_NUM,
#endif
HalIrqVersion(), __DATE__, __TIME__,\
KERNEL_NAME, KERNEL_MAJOR, KERNEL_MINOR, KERNEL_PATCH, KERNEL_ITRE, buildType);
里面的
HalIrqVersion
函数用到的GIC
的虚拟地址,要正确设置,否则没有打印信息。
IMX6ULL
的内存映射代码里,设备空间从GIC
开始映射,所以GIC
的虚拟地址就是PERIPH_DEVICE_BASE
:
// kernel/liteos_a/kernel/base/include/los_vm_zone.h
#define GIC_VIRT_BASE PERIPH_DEVICE_BASE
// vendor/democom/demochip/board/include/asm/platform.h
#define GIC_BASE_ADDR (GIC_VIRT_BASE)