嵌入式系统设计与应用
Catalogue
嵌入式系统基础
嵌入式系统的定义
嵌入式系统是以应用为中心,以计算机技术为基础,并且软硬件是可裁剪的,适用于对功能、可靠性、成本、体积、功耗等有严格要求的专用计算机系统。
嵌入式系统的特点
1)系统功耗低、体积小、专用性强
2)集成度高
3)软硬件高效,系统精简,量体裁衣,去除冗余
4)软件一般固化在存储器芯片中
5)一般需有一套开发工具和环境才能进行开发
嵌入式系统的体系结构
常用存储设备
SRAM:静态的随机存取存储器,加电情况下,不需要刷新,数据不会丢失,而且,一般不是行列地址复用的。
SDRAM:同步的 DRAM,即数据的读写需要时钟来同步。
代表性操作系统
1、VxWorks(昂贵)
2、Windows CE(庞大)
3、嵌入式 Linux
4、μC/OS - Ⅱ
5、Android
嵌入式硬件体系结构
嵌入式微处理器的组成(CPU)
寄存器
寄存器(register)是 CPU 的组成部分,是 CPU 内部用来存放数据的一些小型存储区域,用于暂时存放参与运算的数据和运算结果。
流水线提高运行效率
定义:是指将一个重复的时序过程分解成为若干个子过程,而每一个子过程都可有效地在其专用功能段上与其他子过程同时执行。
特点:
- 流水线过程由多个相互关联的子过程组成,每个子过程称为流水线的“级” 。各个功能段所需时间应尽量相同,可以避免流水线的“堵塞”和“断流”
- 流水技术适合于大量重复的时序过程。
嵌入式 Linux 操作系统
Linux 内核是整个 Linux 系统的灵魂,linux 系统的能力完全受内核能力的制约。
Linux 常用操作命令
- 文件目录相关命令
ls 、cd、pwd、chmod、mkdir、rmdir、rm、cp、mv - 磁盘及系统操作
fdisk、free、df 、mount - 打包压缩相关命令
gzip 、tar(打包并压缩) - 与网络相关命令
ifconfig、ping
rmdir # 删除空目录
rm –r # 删除目录须加-r,表示递归删除, -f表示强制删除
chmod +x 1.txt
chmod 666 2.txt
tar -cvzf # 用gzip的格式压缩并打包
tar –xzvf # 用gzip的格式解压缩并解包
GCC 执行过程
GCC 的基本用法和选项
gcc [options] [filenames]
-c:只编译,不连接成为可执行文件。
-o :指定输出文件的名称,同时这个名称不能和源文件同名。
-g:产生符号调试工具(GNU 的 gdb)所必要的符号资讯,若想对源代码进行调试,我们就必须加入这个选项。
-S:生成汇编代码。
-E:预处理,GCC 预处理后停止编译。
嵌入式 Linux 程序开发基础
makefile
makefile 规则
target: dependency_files
(tab) command
- target: 需要由 make 工具创建的目标体,通常是目标文件或可执行文件;
- dependency_files: 要创建的目标体所依赖的文件; command: 创建每个目标体时需要运行的命令。
Example
![](https://images.hayneschen.top/i/2024/01/11/zotsz1-2.png =x150)
sum: ex_sum.o mysum.o
gcc ex_sum.o mysum.o -o sum
ex_sum.o: ex_sum.c
gcc -c ex_sum.c
mysum.o: mysum.c mysum.h
gcc -c mysum.c
自定义变量
采用变量前
main: main.o mysum.o mymul.o
gcc -o main main.o mysum.o mymul.o
main.o:main.c
gcc -c main.c
mysum.o:mysum.c
gcc -c mysum.c
mymul.o:mymul.c
gcc -c mymul.c
采用变量后
OBJS= main.o mysum.o mymul.o
CC=gcc
main:$(OBJS)
$(CC) -o main $(OBJS)
main.o:main.c
$(CC) -c main.c
mysum.o:mysum.c
$(CC) -c mysum.c
mymul.o:mymul.c
$(CC) -c mymul.c
自动变量
OBJS= main.o mysum.o mymul.o
CC=gcc
main:$(OBJS)
$(CC) -o main $(OBJS)
main.o:main.c
$(CC) -c main.c
mysum.o:mysum.c
$(CC) -c mysum.c
mymul.o:mymul.c
$(CC) -c mymul.c
OBJS= main.o mysum.o mymul.o
CC=gcc
main:$(OBJS)
$(CC) -o $@ $^
main.o:main.c
$(CC) -c $<
mysum.o:mysum.c
$(CC) -c mysum.c
mymul.o:mymul.c
$(CC) -c mymul.c
提示
$^
所有不重复的依赖文件,以空格分开$@
目标文件的完整名称$<
第一个依赖文件的名称
makefile 隐式规则
- 隐式规则是指能够告诉 make 使用默认的方式来完成编译任务,而不必详细指定编译的具体细节,只需把目标文件列出即可。
- Make 会自动按隐式规则来确定如何生成目标文件。
- 适用于使用频率非常高的语句
使用隐式规则
sum: ex_sum.o mysum.o
gcc ex_sum.o mysum.o -o sum
不使用隐式规则
sum: ex_sum.o mysum.o
gcc ex_sum.o mysum.o -o sum
ex_sum.o: ex_sum.c
gcc -c ex_sum.c
mysum.o: mysum.c mysum.h
gcc -c mysum.c
Shell 脚本
- shell 脚本文件结构
shell 脚本文件结构格式是固定的,看一个示例:
#! /bin/bash
echo "Hello World!"
shell 脚本文件的第 1 行必须以符号“#!”开头,表示使用 /bin/bash 解释器来解释并执行程序。
- shell 脚本文件设置可执行权限
chmod +x [文件名]
例如:chmod +x hello.sh
执行已经编译过了的 shell 脚本文件
[root@localhost shell] # ./hello.sh用户变量
- 变量赋值
a="hello world"
(赋值号=
的两侧不允许有空格) - 获取变量的值
在变量前面添加$
符号。
例如:echo $a
或使用(()),取值时可以省略“$”符号
例如:if(( i%3 == 0 ))
- if 条件语句
if (条件表达式)
then
#语句块
elif (条件表达式)
then
#语句块
else
#语句块
fi
提示
条件表达式,用(())
双括号,表达式涉及到条件判断与逻辑运算符与 C++一致
如 if (((i%2==0)&&(i%4!=0)))
- for 循环语句
for (循环变量=初值;循环条件表达式;循环变量增量)
do
#循环体语句块
done
用 shell 脚本编写显示 20 以内能被 3 整除的数
#! /bin/bash
for((i=1; i<20; i++))
do
if(( i%3 == 0 ))
then
echo $i
fi
done
嵌入式系统开发环境的建立
交叉编译
交叉编译是在一个平台上生成可以在另一个平台上执行的代码。
如果本地编译,同时本地执行则对应的编译器是 gcc
如果本地编译,但 ARM 平台执行,则对应的交叉编译器是 arm-linux-gcc
例如:# arm-linux-gcc –o hello hello.c
嵌入式 Linux 文件与进程控制
文件处理
提示
int open(const char *pathname, int oflag, int perms )
- pathname:打开或创建的文件名及路径;
- oflag:选项操作,常用选项如下所示:
O_RDONLY:只读
O_WRONLY:只写
O_RDWR:读写
O_CREAT:若文件不存在自动创建该文件,同时设置 perms 参数
O_TRUNC:若文件存在并且以可写的方式打开时, 文件清 0
O_APPEND:当读写文件时会从文件尾开始移动, 也就是所写入的数据会以附加的方式加入到文件后面。 - perms:被打开文件的存取权限,采用八进制
进程
- 进程是一个具有独立功能的程序的一次动态执行过程。即,进程就是正在执行的程序。
- 进程与程序是两个不同的概念,程序是永久的、静态的,进程是暂时的、动态的。
一个进程,主要包含三个元素:
- 一个可以执行的程序;
- 和该进程相关联的全部数据(包括变量,内存空间,缓冲区等等);
- 程序的执行上下文(进程控制块)
system 函数可以在一个程序的内部启动另一个程序,从而创建一个新进程。
函数原型为:int system(const char *string);
例如: system("gcc -o Hello Hello.c");
System 应用
int main(){
/* 调用system函数编译命令 */
system("gcc -o Hello Hello.c");
printf("Hello.c Compile successfully! \n");
if(fork()==0){
/* 调用execlp函数执行运行程序命令 */
if( execlp("./Hello", "Hello", NULL) < 0){
printf("execlp error\n");
}
}
sleep(1); /* 延时1秒,以等待Hello的输出结果 */
printf("Hello run successfully!\n");
}
通信方式
管道
通过管道实现两个进程间的通信
- 父进程调用 pipe 开辟管道,得到两个文件描述符指向管道的两端。
- 父进程调用 fork 创建子进程,那么子进程也有两个文件描述符指向同一管道。
- 父进程关闭管道读端,子进程关闭管道写端。父进程可以往管道里写,子进程可以从管道里读,管道是用环形队列实现的,数据从写端流入从读端流出,这样就实现了进程间通信。
Q&A
父进程要关闭管道读端,并且子进程要关闭管道写端,原因如下:
程序是要模拟父进程和子进程的管道读写操作,其中父进程用于向管道中写入数据,子进程用于向管道中读取数据,因此开始要关闭父进程的读文件描述符 filedes[0], 以及关闭子进程的写文件描述符 filedes[1],这是为了模拟这个过程。
为什么父进程关闭管道的读文件描述符 filedes[0]后子进程还能读取管道的数据?
是因为系统维护的是一个文件的文件描述符表的计数,父子进程都各自有指向相同文件的文件描述符,当关闭一个文件描述符时,相应计数减一,当这个计数减到 0 时,文件就被关闭,因此虽然父进程关闭了其文件描述符 filedes[0],但是这个文件的文件描述符计数还没等于 0,所以子进程还可以读取。
也可以这么理解,父进程和子进程都有各自的文件描述符,因此虽然父进程中关闭了 filedes[0],但是对子进程中的 filedes[0]没有影响
文件表中的每一项都会维护一个引用计数,标识该表项被多少个文件描述符(fd)引用,在引用计数为 0 的时候,表项才会被删除。所以调用 close(fd)关闭子进程的文件描述符,只会减少引用计数,但是不会使文件表项被清除,所以父进程依旧可以访问
Example
wait(NULL):等待子线程结束
当一个父进程调用 wait(NULL)
时,它会发生以下事情:
- 阻塞父进程:如果没有任何子进程已经结束,
wait(NULL)
会阻塞父进程,直到至少有一个子进程结束。 - 回收子进程:一旦有子进程结束,
wait(NULL)
会回收该子进程的资源。这意味着操作系统会清理与该子进程相关的所有资源,比如内存和进程控制块。 - 不指定子进程:由于
wait(NULL)
不指定等待特定的子进程,它适用于等待任何一个子进程。如果需要等待特定的子进程,可以使用waitpid(pid, &status, options)
函数,其中pid
是特定子进程的进程号。
共享内存
实现步骤:
- 创建共享内存:在内核空间创建,从内存中获得一块共享内存区域,使用函数 shmget()来创建共享内存;
- 映射共享内存:进程的地址空间映射共享内存,也就是把这块创建的共享内存区域映射到进程空间中,使用的函数 shmat()来映射共享内存。
- 解除映射:每个进程使用完内存后,使用 shmdt()解除映射,只有解除才能回收内存。
- 回收内存:最后不使用的进程进行回收,使用函数 shmctl()回收。
创建一个共享内存区域-写入进程
使用 ipcs –m
查看共享内存
读取共享内存的数据-读出进程
串口
起始位和停止位
- 起始位:发送方为了告诉接收方,新的数据字节分组到达,在每一个数据字节分组前面有一个起始位(通常是 0)
- 停止位:为了让接收方知道字节已经结束,在每一个数据字节分组后面有一个停止位(可以是 1~2 位,通常值为 1)。接收方一旦检测到停止位,会一直等待,直到下一个开始位。
数据位
- 当接收端收到起始位后,开始接收数据位。数据位的个数可以是 5 ~ 8 位,一般设置为 8 位。
- 在数据传送过程中,数据位从最低有效位开始传送。
奇偶校验位
数据位发送完后,为了保证数据的可靠性,还要再传送一个奇偶校验位。奇偶校验用于差错检测。如果选择偶校验,则数据位和奇偶位的逻辑“1”的个数必须为偶数,相反,如果是奇校验,数据位和奇偶位的逻辑“1”的个数为奇数。
波特率
- 通信线路上单位时间内传送码元(位)的数目称为波特率(单位:bps)。
- 对于大多数嵌入式设备来说,其波特率都设置为 115200。
嵌入式 Linux 网络应用开发
Socket
int socket(int family,int type,int protocol)
参数 | 意义 | 备注 |
---|---|---|
family | 指定协议族 | 常用的是 AF_INET(IPv4)和 AF_INET6(ipv6) |
type | 套接字类型 | SOCK_STREAM(字节流套接字 TCP) SOCK_DGRAM(数据报套接字 UDP) SOCK_RAW(原始套接字) |
protocol | 一般是 0 | 如果不是原始套接字,一般为 0 |