嵌入式实践实习知识

大二下时长一周半多半周的嵌入式实习,由粤嵌的工程师教授知识。

环境及工具:Ubuntu 18.04,arm-linux-gcc 5.4.0,粤嵌GEC6818开发板

以下是此次实习中get到的一下知识以及遇到的一些问题。

2023.05.09

gcc : c语言编译器

g++ : c++编译器

gdb : c/c++调试器

binutils : 二进制工具集

MinGW : MinGW 是让Windows 用户可以用上 GNU 工具,比如GCC。

Cygwin : Cygwin 提供完整的类Unix 环境,Windows 用户不仅可以使用GNU 工具,理论上Linux 上的程序只要用Cygwin 重新编译,就可以在Windows 上运行。

  • 如果程序只用到C/C++ 标准库,可以用MinGW 或Cygwin 编译。
  • 如果程序还用到了POSIX API,则只能用Cygwin 编译。

cmd 输入 sysdm.cpl 打开环境变量设置

cygwin64 和 MinGW 的 gcc 和 g++ 只能有一个能使用,不能同时使用。

通过修改环境变量的优先级 顺序从上到下,这里使用 cygwin64 的 gcc 和 g++ 如下:

md3

md01

md02

在Linux系统中,可执行文件没有统一的后缀,系统从文件的属性来区分可执行文件和不可执行文件。而gcc则通过后缀来区别输入文件的类别,下面介绍gcc所遵循的部分约定规则。

.c 为后缀的文件,C语言源代码文件;
.a 为后缀的文件,是由目标文件构成的库文件;
.C .cc 或 .cxx 为后缀的文件,是C++源代码文件;
.h 为后缀的文件,是程序所包含的头文件;
.i 为后缀的文件,是已经预处理过的C源代码文件;
.m 为后缀的文件,是Objective-C源代码文件;
.o 为后缀的文件,是编译后的目标文件;
.s 为后缀的文件,是汇编语言源代码文件;
.S 为后缀的文件,是经过预编译的汇编语言源代码文件

sudo:super user do 超级用户执行 ;su:switch user 切换用户。

sudo su:当前用户暂时申请 root 权限,输入的是当前用户密码。

sudo -s:执行环境变数中的 SHELL 所指定的 shell ,或是 /etc/passwd 里所指定的 shell,输入当前用户的密码。

su 用户名::用户名为空时默认为 root 用户,申请切换 root 用户,需要 root 用户的密码,ubuntu 中默认没有设置 root 用户的密码,第一次使用需要自行设置,sudo passwd root 设置 root 用户密码。

su -用户名

  • 如果加入了 - 参数,那么是一种 login-shell 的方式,意思是说切换到另一个用户 <user_name> 之后,当前的 shell 会加载 <user_name> 对应的环境变量和各种设置;

  • 如果没有加入 - 参数,那么是一种 non-login-shell 的方式,意思是说我现在切换到了 <user_name>,但是当前的 shell 还是加载切换之前的那个用户的环境变量以及各种设置。

由下图可知,su 和 -su 都需要输入 root 用户的密码,su 执行后目录为当前用户的目录- su 执行完后目录是根目录,两者都没有时间限制。

md04

由下图可知,使用 sudo 时输入的密码为当前用户的密码,sudo -s 执行后不加载用户变量,目录为当前目录 ,sudo -i 执行完目录为根目录,sudo su 这种用法好像可以舍弃。。。。

md05

另外,用户名的颜色即上面的绿色和灰色好像没什么作用,颜色可以通过命令修改。

gcc 编译的文件 or gcc 编译的文件 -o 生产的文件名

./ 生成的文件名 即可输出信息。

md06

df -h 查看内存

ls -a 查看当前目录下的文件,相比直接 ls 还显示了隐藏文件。

ls -l 查看某一个目录会得到一个7个字段的列表。

md09

文件类型

    • 表示普通文件;
  • d 表示目录;
  • l 表示链接文件;
  • p 表示管理文件;
  • b 表示块设备文件;
  • c 表示字符设备文件;
  • s 表示套接字文件;

文件名称,字体颜色

  • 如果是一个符号链接,那么会有一个 “->" 箭头符号,后面根一个它指向的文件名;

  • 灰白色表示普通文件;

  • 亮绿色表示可执行文件;

  • 亮红色表示压缩文件;

  • 灰蓝色表示目录;

  • 亮蓝色表示链接文件;

  • 亮黄色表示设备文件;

chmod 权限值 文件名:设置用户对文件的权限。

Linux/Unix 的文件调用权限分为三级 : 文件所有者(Owner)、用户组(Group)、其它用户(Other Users)。

1
[ugoa...][[+-=][rwxX]...][,...]

md08

符号模式

who 用户类型 说明
u user 文件所有者
g group 文件所有者所在组
o others 所有其他用户
a all 所有用户, 相当于 ugo
Operator 说明
+ 为指定的用户类型增加权限
- 去除指定用户类型的权限
= 设置指定用户权限的设置,即将用户类型的所有权限重新设置
模式 名字 说明
r 设置为可读权限
w 设置为可写权限
x 执行权限 设置为可执行权限
X 特殊执行权限 只有当文件为目录文件,或者其他类型的用户有可执行权限时,才将文件权限设置可执行
s setuid/gid 当文件被执行时,根据who参数指定的用户类型设置文件的setuid或者setgid权限
t 粘贴位 设置粘贴位,只有超级用户可以设置该位,只有文件所有者u可以使用该位

八进制语法

文件或目录的权限位是由 9 个权限位来控制,每三位为一组。

# 权限 rwx 二进制
7 读 + 写 + 执行 rwx 111
6 读 + 写 rw- 110
5 读 + 执行 r-x 101
4 只读 r– 100
3 写 + 执行 -wx 011
2 只写 -w- 010
1 只执行 –x 001
0 000

eg:765 表示所有者的权限为 rwx(4+2+1),用户组的权限为 rw(4+2+0),其他用户的权限为 rx(4+0+1)。

md07

tar -xvf xxx.tar.gz 文件目录

tar -zcvf xxx.tar.gz a.txt b.txt c.txt 将 a.txt b.txt c.txt 打包压缩为一个名为 xxx.tar.gz 压缩包。

tar cvf xxx.tar xxx 打包一个tar

tar xvf xxx.tar xxx 解开一个tar

tar cvzf xxx.tar.gz xxx 打包压缩一个 tar

tar zxvf xxx.tar.gz 解压一个tar

tar -tf 误解压文件 | xargs rm -rf

tar -tf 是列出该压缩文件中的文件列表,xargs rm -rf 则是根据前面的文件列表来删除文件。

在早期,Linux 和 Unix 系统用一种叫做 tar(Tape ARchive,磁带归档)的工具来打包文件。但它只能打包不能压缩。后来 gzip(GNU Zip)就出现了,通过将 tar 和 gzip 结合起来,形成 tar.gz 格式。

tar 格式在保留文件权限、所有权和时间戳。

2023.5.10

删除文件的骚操作:

rm 文件1 文件2 … 删除多个文件

*rm .txt 删除 .txt 这一类文件

rm xxx 删除固定字母开头的文件*

rm * -rf 删除文件夹下面所有文件

Activator 软件,连接开发板,传输在 Ubuntu 下交叉编译后生成的文件到开发板的系统下。

1
2
3
输入: rx 文件名
然后将文件拖进去
选择 Xmodem

md14

执行文件时需要先查看文件是否可执行。执行以下命令让文件权限变为可读可写可执行文件

1
chmod 777 文件名

2023.5.11

1
2
3
man man 查看所有手册
man -f open 可查看 open 关键字出现在哪些手册中
man 2 open 在第二手册中查询 open 关键字

文件描述符本质

函数 open() 的返回值,是一个整型 int 数据。这个整型数据,实际上是内核中的一个称为 fd_array 的数组的下标:

md16

打开文件时,内核产生一个指向 file{} 的指针,并将该指针放入一个位于 file_struct{} 中的数组fd_array[] 中,而该指针所在数组的下标,就被 open() 返回给用户,用户把这个数组下标称为文件描述符,如上图所示。

结论:

  • 文件描述符从 0 开始,每打开一个文件,就产生一个新的文件描述符(一般是返回一个最小未使用的描述符)。
  • 可以重复打开同一个文件,每次打开文件都会使内核产生新结构体(每一个描述符拥有自己独立的资源互不影响),并得到不同的文件描述符。
  • 由于系统在每个进程开始运行时,都默认打开了一次键盘、两次屏幕,因此0、1、2描述符分别代表标准输入、标准输出和标准出错三个文件(两个硬件)

md17

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int main()
{
// 1. 打开文件
// int open(const char *pathname, int flags);
int file_descriptor = open("test_open.txt", O_RDWR);
int file_descriptor1 = open("test_open.txt", O_RDWR);
// 1.1 判断是否打开成功
if (file_descriptor == -1)
{
// printf("文件打开失败\n");
perror("文件打开失败\n");
return -1;
}
if (file_descriptor1 == -1)
{
// printf("文件打开失败\n");
perror("文件打开失败\n");
return -1;
}
printf("文件打开成功\n");
printf("%d\n",file_descriptor); // 3
printf("%d\n",file_descriptor1); // 4
}

从上面可以得出虽然打开了同个文件,但是得到了不同文件描述。

在 Ubuntu 下直接 gcc 编译文件跟 arm-linux-gcc 编译文件是不一样的,后者为交叉编译。

GEC6818开发板屏幕的色深是 32 位的,32 位色深的屏幕一般被称为真彩屏,或 1600万色屏。色深决定了一个像素点所能表达的颜色的丰富程度,色深越大,色彩表现力越强。

虽然LCD设备本质上也可以看作是一个文件,在文件系统中有其对应的设备节点,可以像普通文件一样对其进行读写操作(read/write),但由于对字符设备的读写操作是以字节流的方式进行的,因此除非操作的图像尺寸刚好与屏幕尺寸完全一致,如下图所示,图片的宽高与LCD的宽高完全一致,否则将会画面会乱

md15

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main()
{
// 1. 打开文件
// int open(const char *pathname, int flags);
int file_descriptor = open("/dev/fb0", O_RDWR);

// 1.1 判断是否打开成功
if (file_descriptor == -1)
{
perror("文件打开失败\n");
return -1;
}
printf("文件打开成功\n");

// 2. 配置颜色值 ARGB 每个通道占 1 字节 则总需要 4 个字节 用 int 类型刚好
int color = 0x00CC99FF;

// 3. 将颜色数据直接线性灌入LCD设备节点
for (int i =0; i < 800 * 480; i++)
{
write(file_descriptor, &color, sizeof(int));
}
printf("lcd文件写入完毕\n");


// 4. 关闭文件
int ret = close (file_descriptor);
if (ret == -1)
{
perror("文件关闭失败\n");
return -1;
}
printf("文件关闭成功\n");

return 0;
}

像上述代码这样,直接将数据通过设备节点 /dev/fb0 写入的话,这些数据会自动地从LCD映射内存的入口处(对应LCD屏幕的左上角)开始呈现,并且会以线性的字节流形式逐个字节往后填充,除非图像尺寸与显示器刚好完全一致,否则显示是失败的。

一般而言,图像的尺寸大小是随机的,因此更方便的做法是为LCD做内存映射,将屏幕的每一个像素点跟映射内存一一对应,而映射内存可以是二维数组,因此就可以非常方便地通过操作二维数组中的任意元素,来操作屏幕中的任意像素点了。这里的映射内存,有时被称为显存

md16

如上图所示,将一块内存与LCD的像素一一对应:

  • LCD上面显示的图像色彩,由其对应的内存的数据决定
  • 映射内存的大小大于等于LCD的真实尺寸大小
  • 映射内存的大小可以大于LCD的真实尺寸,有利于优化动态画面(视频)体验

将原来代码修改如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 3. 映射内存来进行LCD屏幕显示
char *p = mmap(NULL, 800 * 480 * 4, PROT_WRITE | PROT_READ, MAP_SHARED, file_descriptor, 0);
if (p == NULL)
{
perror("mmap error");
return -1;
}

for (int i = 0; i < 800 * 480; i++)
{
// memcpy(p + i * 4, &color, sizeof(int));
*(p + i) = color;
}

printf("lcd文件写入完毕\n");
munmap(p, 800 * 480 * 4); // 解除映射

2023.5.12

实战项目:

要求:使用C语言代码编写菜单选项并实现其对应的功能
例:

--------欢迎使用…操作系统-------(使用switch语句)
1.德国国旗
2.法国国旗
3.瑞士国旗
4.日本国旗(公式:(x-x0)(x-x0) + (y-y0)(y-y0) = r*r)
5.土耳其国旗(星星可以不用显示,选做加分)
6.各国国旗幻灯片形式播放(1s)
7.退出(break/return)

2023.5.13

图片深度,一般有8bit、16bit、24bit 和 32bit等。

8bit:

28=22(B)23(G)23(R)=2562^8 = 2^2(B)*2^3(G)*2^3(R)=256

取值范围:0~255,总共可显示 256 种颜色。

16bit:

216=25(B)26(G)25(R)=655362^{16} = 2^5(B)*2^6(G)*2^5(R)=65536

24bit:

224=28(B)28(G)28(R)=167772152^{24} = 2^8(B)*2^8(G)*2^8(R)=16777215

32bit:Alpha透明度 + 24 bit,ARGB。

为什么不使用常见的 jpg文件呢,因为 jpg文件为了便于能在网络上传播,经过了压缩处理,直接读取数据的话是读不到颜色数据的,需要经过解码才可以读出颜色数据;而 BMP文件是微软公司推出的一种图片数据格式,这种文件未经过压缩处理,读取颜色数据的时候无需进行解码,因此它的文件大小一般也很大。

BMP位图格式

位图的文件头 Bitmap File Header (14 bytes):

md26

因此检查前两个字节即可得知此文件是否为 bmp文件,第一个字节为 0x42,第二个字节为 0x4D。

位图信息数据头 DIB Header (54 bytes):

md27

bmp文件存储的数据为高位在前低位在后,这就是为什么前面第一个字节反而是 0x42,第二个字节为 0x4D,内容42 4D 对应了ASCII表的 ”BM“两个字母。

如一张 10 * 10的bmp图像

md28

在文件中数据顺序如下:另外 **bmp存储颜色数据为 BGR 顺序。**如下列红色画出 0000 ff表示的是红色。

md29

因此在读取 bmp文件的 颜色数据后需要将 BGR通道转为 ARGB通道显示在 lcd屏幕(32bit);若为 24bit的 lcd屏幕,则转为 RGB通道即可;另外由于 bmp文件数据是高位在前低位在后,直接读取转换显示完图像是倒置的,需要将颜色数据从尾开始读取才可正常显示。

2023.5.15

使用U盘为开发板传输文件:

如果 U盘是 fat32文件系统格式的,U盘可以直接挂载(mount)到到开发板。

  • 在开发板系统中进入中 /mnt/udisk 可看到插入U盘的信息;

如果 U盘是 NTFS格式的,系统不会自动完成挂载,可以通过手动挂载,将 U盘挂载到 /mnt/udisk/ 目录下,然后通过该目录找到U盘。

1
2
输入命令:
mount -t ntfs /dev/sda1 /mnt/udisk

2023.5.16

播放音乐和视频

音频或视频都通过某一个规则进行压缩和解码,我们的程序可以通过调用播放器来实现压缩和解码的过程,我们的程序主要用于控制播放、暂停、快进、快退等的一些操作即可。
开发板中默认有一个播放器 madplay 但是它只能用于播放音频文件MP3,不可以播放视频文件。因此可以从外部移植一个 mplayer 到开发板中,mplayer 则可以进行播放音频、视频文件
1.检查开发板中是否已经存在播放器 ( /bin/ /usr/bin/) which madplay / find -name madplaya如果不存在则手动移植 (复制)进去下载到mplayer程序文件到开发板后需要给它赋予可执行权限

1
2
chmod 111 mplayer
mplayer xxx.avi mplayer 文件名可以是视频文件,也可以是音频文件

在虚拟机里面输入命令 kill -l 可以查看当前系统可以用的信号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$ kill -l 
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX
1
2
3
 9) SIGKILL    杀死某一个进程
18) SIGCONT 给某一个进程发送继续运行的信号
19) SIGSTOP 给某一个进程发送暂停运行的信号
1
2
3
4
5
6
7
8
9
10
11
注意:两个不同的播放器

killall -9 mplayer // 给当前系统中所有进程名为 mplayer 的进程发送 9号信号 (杀死)
system("killall -19 mplayer"); // 暂停播放
system("killall -18 mplayer"); // 继续播放
system("killall -9 mplayer"); // 结束播放器

killall -9 madplay // 给当前系统中所有进程名为 madplay 的进程发送 9号信号 (杀死)
system("killall -19 madplay"); // 暂停播放
system("killall -18 madplay"); // 继续播放
system("killall -9 madplay"); // 结束播放器

视频:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
void send_cmd(int fifo_fd,char *cmd)  //发送指令
{
//写入管道文件
write(fifo_fd, cmd, strlen(cmd));
}

//mplayer初始化
//判断是否存在管道文件,不存在才创建
if(access("/fifo",F_OK)) //默认管道文件创建在根目录,F_OK判断是否存在
{
//条件成立,不存在管道文件
mkfifo("/fifo",777); //创建管道文件
}
//打开管道文件
int fifo_fd = open("/fifo",O_RDWR);
if(fifo_fd == -1)
{
printf("管道文件打开失败\n");
return -1;
}

// 一开始的视频播放
system("mplayer -slave -quiet -geometry 0:0 -zoom -x 800 -y 400./Test_bmp/Test1.avi");
//发送暂停(广义的暂停,即若视频正在播放则暂停播放;若视频暂停播放则开始播放)指令
send_cmd(fifo_fd,"pause\n");
//音量+
send_cmd(fifo_fd,"volume -10\n");
//音量-
send_cmd(fifo_fd,"volume +10\n");
//kill mplayer播放器
system("killall -9 mplayer");

2023.5.17-2023.5.18

项目整合模块

主界面:包含四个模块

  • 电子相册V3

  • 音乐播放器

  • 视频播放器

  • 游戏

选择第一个模块可以进入电子相册界面。进入程序的界面需要点击两次右上角才可退回主界面

电子相册界面包括:

  • 手动播放:水平滑动和垂直滑动切换图片
  • 循环播放:循环 2 次,循环后自动返回到电子相册界面;时间原因还未设置手动退出选项。

音乐播放器界面包括:

  • 播放音乐
  • 暂停音乐
  • 上一首音乐
  • 下一首音乐

视频播放器界面包括:

  • 开始
  • 暂停
  • 继续
  • 调高音量
  • 调低音量

游戏界面 :雷霆战机

总结

  • 项目分模块写的时候需要考虑项目的耦合性,尽量封装成一个函数便于他人可直接调用;
  • 编写代码时尽量少用全局变量,过多使用全局变量一方面会增加程序运行的内存,也会降低代码的耦合性,加大项目整合模块的难度;
  • 若声明了全局变量,切忌在函数中再次声明定义同个变量,否则会导致不必要的麻烦;
  • 目前的编程能力太弱了,有待提高,使用的代码都是很低级的并且重复性很高,后续应当通过许多编程小项目提高自己的编程能力。

sudo -i和sudo -s - 乌瑟尔 - 博客园 (cnblogs.com)

(49条消息) Linux-FrameBuffer双缓冲机制显示图像_fb双缓冲_干燥剂007860的博客-CSDN博客

浅谈图像格式 .bmp - 知乎 (zhihu.com)

https://blog.csdn.net/m0_57266121/article/details/118979056

(50条消息) 利用GEC6818屏幕 显示一张bmp图片_read(bmp_fd,&bpp,2);_三目条件的博客-CSDN博客