嵌入式开发

串口设置:minicom

简介

多数嵌入式系统都通过UART进行初级引导,在未进入嵌入式系统时,屏幕和键盘外设的驱动都没有挂载,无法像在 PC 安装系统一样通过屏幕键盘进行安装,这时只能通过串口连接 PC,在 PC 和嵌入式设备之间进行串口通讯,来进一步引导安装系统。

minicom是Linux系统下的串口通讯工具,所以在嵌入式开机之前所有对其操作都通过 PC 的 minicom 来进行。

使用

# minicom -s

进入 minicom 设置,设定 PORT 为 /dev/ttyUSB0

选择 "Save setup as dfl" 保存设置,之后每次可以直接进入 minicom 就会自动连接 USB0 串口

# minicom     

简单文件传输:TFTP

简介

minicom 可以用来进行字符级的通讯,用来给嵌入式设备传输命令,但是无法用于文件的传输。TFTP 是基于 UDP 的简单文件传输协议,通过 TFTP 可以将文件从 PC 传输给嵌入式设备。之后的内核,文件系统都会通过 TFTP 传输。

使用

在 PC 端安装 tftp-server

配置文件在/etc/xinetd.d/tftp

service tftp
{
   socket_type = dgram
   protocol = udp
   wait = yes
   user = root
   server = /usr/sbin/in.tftpd
   server_args = -c -s srv/tftpboot
   disable = no
   per_source = 11
   cps = 100 2
}

其中 src/tftpboot 为tftp存放文件的目录

# /etc/rc.d/init.d/xinetd restart

PC(server)开启 tftp-server 服务后,通过 minicom 来配置嵌入式端(client)
按嵌入式设备上的 reset 物理按键进入 Boot(需在引导过程中通过回车打断)

set ipaddr 192.168.208.224
set serverip 192.168.208.47
tftp target_address file_name //example: tftp 0x82000000 zImage3.8.13

NFS服务器架设:NFS

简介

嵌入式设备内部空间是有限且珍贵的,同时我们在开发初期也不知道我们最终需要多少内存,所以把开发内容放在 PC 端,使嵌入式设备通过挂载的方式进行连接。NFS 是一种网络文件系统,由 Sun 公司开发发展起来,用于不同操作系统之间通过网络共享文件,满足我们跨系统开发的要求。

使用

在 PC 端开启 portmap 和 NFS 两个服务:

# chkconfig nfs on
# chkconfig portmap on
# service nfs restart
# service portmap restart

服务器的共享目录和权限在 /etc/exports 中设定

/home/student/lizhihao6/bbb/ 192.168.*.*(rw, no_root_squash)

使用如下命令使修改生效

# exportfs -a

在进入嵌入式设备系统后,通过 minicom 挂在主机NFS

# ifconfig eth0 192.168.208.224 netmask 255.255.255.0
# ping 192.168.208.24
# mkdir /home/
# mkdir /home/bbb/
# mount -t nfs 192.168.208.24:/home/student/lizhihao6/bbb/ /home/bbb/ -o nolock -o proto=tcp

引导加载器

引导加载器:BootLoader

简介

BootLoader 分为 Boot 和 Loader。Boot 用于上电自检,之后 Loader 从 MBR 中读取 OS Boot Loader 来获取系统,最后进入系统。

我们的 OS Boot Loader 由 TFTP 传入,最后再用 Boot args 来引导进入系统。

关于更详细的启动流程可以参考这个Blog

使用

开启 minicom ,按嵌入式设备上的 reset 物理按键进入 Boot(需在引导过程中通过回车打断)

传入内核镜像、文件系统镜像、设备树文件镜像

u-boot # tftp 0x82000000 zImage3.8.13
u-boot # tftp 0x88080000 ramdisk_img.gz 
u-boot # tftp 0x88000000 am335x-boneblack.dts

设置 Boot 参数

u-boot # set bootargs console=ttyO0,115200 root=/dev/ram rw initrd=0x88080000,8M

Boot 启动

u-boot # bootz 0x82000000 - 0x88000000

bootX < kernel address > < rootdisk address > < fdt(dts file) address >

内核配置和编译

内核配置:make menuconfig

简介

整个嵌入式设备的启动流程,以及 PC 和嵌入式的命令、文件传输已经做完,下一步就是制作嵌入式设备的系统文件了。

系统分为三部分:

  • 内核 zImage3.8.13
  • 文件系统 ramdisk_img.gz
  • 设备树 am335x-boneblack.dts

其中设备树文件可以直接开源找到,这节主要讲述内核的制作,下一小节主要讲述文件系统的制作。

内核我们采用开源的 linux-4.4

使用

首先把文件初始化为bbb的默认配置文件 bb.config

# ARCH=arm make bb.org_defconfig

然后进入配置文件选项化编辑

# ARCH=arm make menuconfig

在这里我们为了和之后的 RAM disk 的文件系统匹配,开启如下两个选项。

- [Y] /Device Drivers/Block Devices/RAM block device support

- [Y] /General Setup/Initial RAM filesystem and RAM disk

内核编译:make

简介

由于我们的内核是给嵌入式设备使用的,所以要使用交叉编译器进行编译。

同时为加快编译速度,开启 -j8 8线程编译。

使用

# ARCH=arm CROSS_COMPILE=/opt/armhf-linux-2018.08/bin/arm-linux- make -j8

之后就会得到 zImage3.8.13

根文件系统的构建

根文件系统类型:File System

简介

文件系统优点缺点挂载位置
EXT2FS减少额外擦写没有日志ROM or Disk
NFS支持远程挂载访问速度受带宽影响ROM or Remote Disk
JFFS2Flash 损耗平衡擦除速度慢Nor Flash
YAFFS2适配于 Nand Flash擦除速度慢Nand Flash
RAM Disk读写速度极快掉电丢失RAM

这里我们选取 RAM Disk 作为我们的文件系统。

根文件系统制作:BusyBox

简介

BusyBox 是由 Bruce Perens 首先开发的文件系统制作工具,我们通过 BusyBox 构建我们的文件系统

使用

首先配置 BusyBox

# make menuconfig

在 Setting/Build Opition 中

设定动态库编译开启

设定交叉编译器路径
- /opt/armhf-linux-2018.08/bin/arm-linux-

之后编译文件系统

# make -j8
# make install

生成的文件默认在 _install/ 下面

配置 etc 目录:

# mkdir etc
# vim /etc/inittab
---
# /etc/inittab
::sysinit:/etc/init.d/rcS
::askfirst:-/bin/sh
::once:/usr/sbin/telnetd -l /bin/login
::ctrlaltdel:/sbin/reboot
::shutdown:/bin/umount -a -r
---
# vim /etc/rc
---
#!/bin/sh
hostname BeagleBone
mount -t proc proc /proc 
mount -t sysfs sys /sys
/bin/cat /etc/motd
---
# vim /etc/motd
---
Welcome to
======================
    lizhihao6-BBB
======================
---
# chmod +x etc/rc
# mkdir etc/init.d
# cd etc/init.d
# ln -s ../rc rcS

配置 dev 目录:

# mkdir dev
# cd dev
# mknod console c 5 1
# mknod null c 1 3
# mknod zero c 1 5

配置 lib 目录:

# mkdir lib 
# cp /opt/armhf-linux-2018.08/arm-none-linux-gnueabi/lib/libc-2.2* lib/
# cp /opt/armhf-linux-2018.08/arm-none-linux-gnueabi/lib/libm-2.2* lib/
# cp /opt/armhf-linux-2018.08/arm-none-linux-gnueabi/lib/ld-2.2* lib/

// 注意库可能体积超过 8M ,我们之后分配的 ramdisk 大小为 8M,所以这里要对库进行压缩
# cd lib
# /opt/armhf-linux-2018.08/bin/arm-linux-strip libc-2.25.so
# /opt/armhf-linux-2018.08/bin/arm-linux-strip libm-2.25.so
# /opt/armhf-linux-2018.08/bin/arm-linux-strip ld-2.25.so

# ln -s ld-2.25.so ld-linux-armhf.so.3
# ln -s libc-2.25.so libc.so.6
# ln -s libm-2.25.so libm.so.6

如果设置动态库文件系统,这里还要添加几个库:

libpthread-2.25.so
libpthread.so.0 -&gt; libpthread-2.25.so
libresolv-2.25.so
libresolv.so.2 -&gt; libresolv-2.25.so
libresolv.so -&gt; libresolv.so.2
librt-2.25.so
librt.so.1 -&gt; librt-2.25.so
librt.so -&gt; librt.so.1
libthread_db-1.0.so
libthread_db.so.1 -&gt; libthread_db-1.0.so
libthread_db.so -&gt; libthread_db.so.1

配置 proc sys mnt 目录:

# mkdir proc
# mkdir sys
# mkdir mnt

最后便可以制作 ramdisk_img:

# dd if=/dev/zero of=ramdisk_img bs=1k count=8192
# mke2fs ramdisk_img
# mount ramdisk_img
# cp -r ./_install/ /mnt/ramdisk_img/

这里会发现 /dev/ 目录底下的 nod 无法拷贝,需要补充:

# cd /mnt/ramdisk_img/dev/
# mknod console c 5 1
# mknod null c 1 3
# mknod zero c 1 5

最后卸载压缩:

# umount /mnt/ramdisk_img
# gzip ramdisk_img

内核镜像制作完成。

将制作好的 zImage3.8.13 ramdisk_img.gz am335xboneblack.dts 拷贝到 TFTP 默认目录,方便传输。

将嵌入式设备进入Boot,之后配置 TFTP 地址:

u-boot # set ipaddr 192.168.208.224
u-boot # set serverip 192.168.208.24

传输镜像

tftp 0x82000000 zImage3.8.13
tftp 0x88080000 ramdisk_img.gz 
tftp 0x88000000 am335x-boneblack.dts

设置 Boot 参数

u-boot # set bootargs console=ttyO0,115200 root=/dev/ram rw initrd=0x88080000,8M

Boot 启动

u-boot # bootz 0x82000000 - 0x88000000

这时便可以进入系统了

Welcome to
======================
    lizhihao6-BBB
======================
# 

图形用户接口

实现基本画图功能:GUI

简介

显示屏的整个显示区域,在系统内会有一段存储空间与之对应,我们改变显示内容实际上是改变存储空间。

其中每个像素的 RGB 信息 由2或3个字节来表示,也就是说,我们只需要找到存储空间的首地址,然后加上像素位置*RGB信息所占字节的偏移量就可以得到该像素在内存的对应位置,改变该位置的内存就可以改变显示内容。

屏幕及缓存区的信息获取被封装在了 < linux/fb.h > 中,通过 fb.h 获取缓冲区的首地址及 RGB 字节组成。

嵌入式设备有时并不具有显示屏,我们可以用 x11vnc 来建立远程屏幕,模拟显示器。

使用

首先要在编译系统内核时,加入对屏幕驱动的支持,具体更改项参照 diff 文件。

重新编译好内核后查看 fbset:

# fbset
mode &quot;1280x720-0&quot;
    # D: 0.000 MHz, H: 0.000 kHz, V: 0.000 Hz
    geometry 1280 720 1280 720 16
    timings 0 0 0 0 0 0 0 accel true
    rgba 5/11,6/5,5/0,0/0
endmode

之后编写程序实现画点,画圆,显示 bmp 图片

程序在我的github

程序中绘制移动圆的部分一开始采用了每帧先把整个屏幕刷新为黑色,在用 sin (float \theta) cos (float \theta) 来画圆。

这样的显示效果为:

可以看出:有时每一帧圆明显没有绘制完全

使用 time.h 可以发现,绘制全屏黑色和绘制圆形的时间为:绘制全屏黑色绘制圆
0.003653s0.000197s

而以 60 帧/s 的刷新率来计算,一帧的绘制时间要低于 0.0167 s

而当前绘制一帧的总时间为:0.0038s ~ 0.0167s 和一帧绘制时间比较接近,可能频繁导致一帧的内存没有更新完毕,显示器就刷新新的一帧了。

对此主要有两个优化方法:

  • 将上次绘制为白色的圆上的点绘制为黑色,替代全屏刷新为黑色。
  • 采用 Bresenham-Algorithm 加快绘圆速度。

之后绘制一帧只需 0.000012 s

浮点数绘制 1000 次Bresenham绘制 1000 次
0.173038s0.005705s

可见绘制速度有两个数量级的提升。

此时绘制一帧只需 0.000024s 左右,可以完美的在一帧时间内绘制完成。

BMP文件主要分为四部分:

  • bmp文件头(bmp file header):提供文件的格式、大小等信息
  • 位图信息头(bitmap information):提供图像数据的尺寸、位平面数、压缩方式、颜色索引等信息
  • 调色板(color palette):可选,如使用索引来表示图像,调色板就是索引与其对应的颜色的映射表
  • 位图数据(bitmap data):图像数据

绘制 BMP 文件也就是把 BMP 图像数据转化为 RGB 数据,传入显存中。

source image:

show image:

可以看到图片被正确的显示了出来。

合理的软件结构:API

简介

整个 API 分为三部分:

  • utils:包含一些常用工具函数,比如 BMP 转 RGB,浮点数四舍五入
  • frame_utils:包含对显存操作的底层封装,比如读取显存的首地址
  • draw_utils:包含画图的 API,是对 frame_utils 的高级封装

使用

使用静态库编译的 Makefile:

main : main.o utils.o frame_utils.o draw_utils.o
    gcc -o main main.o utils.o frame_utils.o draw_utils.o -lm
utils.o: utils.c
    gcc -c utils.c
frame_utils.o: frame_utils.c
    gcc -c frame_utils.c
draw_utils.o: draw_utils.c frame_utils.h utils.h
    gcc -c draw_utils.c
main.o: main.c frame_utils.h draw_utils.h
    gcc -c main.c
clean :
    rm -f main main.o frame_utils.o draw_utils.o utils.o

使用动态库编译的 Makefile:

main : main.c libdraw.so
    gcc main.c -L. -ldraw -o main 
libdraw.so: utils.c frame_utils.c draw_utils.c frame_utils.h utils.h draw_utils.h
    gcc utils.c frame_utils.c draw_utils.c -fPIC -shared -o libdraw.so -lm
clean :
    rm -f main main.o libdraw.so
得到 main 可执行程序的大小为:静态动态
18K9K

可以看到动态链接库有效的减小了可执行程序的大小,但是同时可能会带来库缺失的问题。

参考

嵌入式Linux系统开发入门 方元-电子工业出版社
Bresenham Algorithm
BMP file format


Time and Tide wait for no man.