编辑
2024-04-20
学习记录
0
请注意,本文编写于 418 天前,最后修改于 314 天前,其中某些信息可能已经过时。

目录

1.使用MQTT实现智能家居-基于所有Linux开发板
1.相关概念
2.开源mqttclient框架分析
3.在Ubuntu上使用MQTT
4.在Linux开发板上使用MQTT
2.物联网视频监控系统
1.两种方案:
2.相关概念
3.MJPG-streamer程序框架:
4.FFmpeg数据传输流程:
5.摄像头和声卡接口
6.流媒体服务器nginx反向代理
7.内网穿透:
3.基于Linux从零写BootLoader
4.uboot移植

1.使用MQTT实现智能家居-基于所有Linux开发板

1.相关概念

MQTT协议全称是Message Queuing Telemetry Transport,翻译过来就是消息队列遥测传输协议,它是物联网常用的应用层协议,运行在TCP/IP中的应用层中,依赖TCP协议,因此它具有非常高的可靠性,同时它是基于TCP协议的 <客户端-服务器> 模型发布/订阅主题消息的轻量级协议,也是我们常说的发送与接收数据。

MQTT适合物联网,类似:订阅电视台某个频道。

image.png

MQTT报文格式:

  1. 固定报头:占两个字节,第一个字节的高4位表示控制报文的类型(两个保留使用14个),低4位表示报文类型的标志位,PUBLISH报文的第一字节bit3是控制报文的重复分发标志(DUP),bit1-bit2是服务质量等级,bit0是PUBLISH报文的保留标志,用于标识PUBLISH是否保留,当客户端发送一个PUBLISH消息到服务器,如果保留标识位置1,那么服务器应该保留这条消息,当一个新的订阅者订阅这个主题的时候,最后保留的主题消息应被发送到新订阅的用户。固定报头的第二个字节开始是剩余长度字段,是用于记录剩余报文长度的,表示当前的消息剩余的字节数。
  2. 可变报头:只有某些报文才拥有可变报头,可变报头的内容会根据报文类型的不同而有所不同。CONNECT报文的可变报头包含四个字段:协议名(Protocol Name)、协议级别(Protocol Level)、连接标志(Connect Flags)(一些标志位,如遗嘱的状态和是否有用户名密码等)以及保持连接(Keep Alive)字段。
  3. 有效荷载:有效载荷也是存在与某些报文中,不同的报文有效载荷也是不一样的,比如:CONNECT报文的有效载荷(payload)包含一个或多个以长度为前缀的字段,可变报头中的标志决定是否包含这些字段。如果包含的话,必须按这个顺序出现:客户端标识符,遗嘱主题,遗嘱消息,用户名,密码 。SUBSCRIBE报文的有效载荷包含了一个主题过滤器列表,它们标识着客户端想要订阅的主题,每一个过滤器后面跟着一个字节,这个字节被叫做服务质量要求(Requested QoS),它给出了服务端向客户端发送应用消息所允许的最大QoS等级。

2.开源mqttclient框架分析

程序设计(分层):

  1. app:根据数据控制设备,while(1){等待消息;处理消息;}
  2. 协议层:负责数据的解析、打包,MQTT/FTP/SSH;
  3. 平台/驱动:负责设备初始化、数据收发,提供定时器/多线程/网卡收发;

客户端程序框架:

  1. 订阅端要做的事情:连接、订阅(主题)、等待;
  2. 发布端要做的事情:连接、发布(主题+消息本身); image.png

1.连接服务器函数调用过程 image.png

2.创建线程调用过程 image.png 其中创建的线程中的函数mqtt_yield_thread会读网络数据,处理数据包,保持心跳

3.发布消息调用过程 image.png

4.订阅消息过程

消息何时到来?不知道!

所以,必定是某个内核线程不断查询网卡:

  • 读网卡数据

    • 得到数据的话就判断、处理 image.png

在第2步创建的线程,while(1){读网络数据;if(是否是一个发布者消息){判断那个topic,并执行对应的函数;}}

3.在Ubuntu上使用MQTT

  1. git clone官方代码(用韦东山的,现在更新的有点问题);
  2. 安装cmake:sudo apt-get install cmake g++
  3. 编译 & 运行:./build.sh
  4. 运行build.sh脚本后会在 ./build/bin/目录下生成可执行文件emqxbaiduonenet等多个平台的可执行程序,直接运行即可:./build/bin/emqx

4.在Linux开发板上使用MQTT

源码直接复现:

  1. 官方代码编译不出来,用韦东山保存的mqttclient代码,进行交叉编译(注意修改代码后要先移除原先编译的build,不然不回更新可执行文件。
  2. 实际操作:
shell
// 1. 创建文件 $ cat arm-linux.cmake set(CMAKE_SYSTEM_NAME Linux) set(CMAKE_C_COMPILER arm-buildroot-linux-gnueabihf-gcc) set(CMAKE_CXX_COMPILER arm-buildroot-linux-gnueabihf-g++) // 2. 修改build.sh cmake .. "-DCMAKE_TOOLCHAIN_FILE=../arm-linux.cmake" // 3. 执行 ./build.sh // 4. 编译库,得到:./libmqttclient/lib/libmqttclient.so ./make-libmqttclient.sh // 5. 修改代码后要先移除build文件夹 rm -rf build

在工程中使用MQTT方法:

  1. 方法1:修改MQTT源码,然后执行rm -rf build和./build.sh
  2. 方法2:使用库
    1. 编译库(./make-libmqttclient.sh),得到:./libmqttclient/lib/libmqttclient.so
    2. 将编译得到的./libmqttclient/include和libmqttclient.so复制出来再在makefile里编译时加上头文件.h的路径、库的路径、其他库的路径。
    3. 将库文件libmqttclient.so拷贝到开发板的/lib目录,开发板就能找到这个库了。
  3. 方法3:把MQTT源码放入自己的工程
    1. 使用makefile来管理,可以使用韦东山提供的makefile模板;
    2. 添加头文件,库的位置等等;

相关信息

(待完善,后续加各种传感器实现智能家居)

2.物联网视频监控系统

1.两种方案:

  1. MJPG-streamer:可以运行在低性能的板子上,对ARM板的性能要求不高,主频200MHz的ARM芯片也能实现。
  2. ffmpeg:比较热门的流媒体方案;

2.相关概念

  1. 流媒体协议:RTMP、HTTP-FLV、HLS
  2. 推流端:ffmpeg;
  3. 流媒体服务器:Nginx;
  4. 拉流端:浏览器/VLC播放器;

3.MJPG-streamer程序框架:

image.png

在imx6ull上运行mjpg推流到本地ip的8080端口:

mjpg_streamer -i "/usr/lib/mjpg-streamer/input_uvc.so -d /dev/video1 -f 30 -q 90 -n" -o "/usr/lib/mjpg-streamer/output_http.so -w /usr/share/mjpg-streamer/www"

4.FFmpeg数据传输流程:

image.png 音视频编解码流程:

5.摄像头和声卡接口

1.摄像头接口(v4l2):

  1. 设置格式:分辨率、图像的格式、帧率;
  2. 启动摄像头;
  3. 得到数据:app向内核请求buffer,将buffer放入队列,摄像头驱动程序将数据存入buffer,app将buffer出列,得到数据。
  4. 停止;

2.声卡接口(ALSA):

  1. 指标:采样频率、采样精度(多少位编码);
  2. 比较复杂需要使用ioctl设置很多参数,所以一般基于alsa-lib来编写app;

6.流媒体服务器nginx反向代理

移植nginx方法:1.下载源码,手工编译。2.使用Buildroot,配置选择nginx,直接编译生成映像文件。

使用Buildroot:

  1. 设置交叉编译工具链
  2. 下载第3方模块:
    1. 在Buildroot目录下,创建目录:mkdir dl/nginx
    2. 使用git下载:cd dl/nginx && git clone https://github.com/winshining/nginx-http-flv-module.git
    3. 在2020年使用GIT下载这个模块时,实验成功。在2023年时失败,可能是这个模块引入了bug。我们执行上述命令得到的是最新的源码,还要执行以下命令取出2020年的源码: cd dl/nginx/nginx-http-flv-module ; git checkout 1ccfee122804b28c60f1f923eee7824a5111680c
  3. 在Buildroot根目录
    1. make menuconfig
    2. 把原来的lighttpd去掉,否则板子也会自动启动它,就会有两个HTTP服务了:lighttpd, nginx
    3. 如图选择Nginx,建议把所有功能都选上
    4. 并且设置额外的参数,在“additional modules”中添加: $(TOPDIR)/dl/nginx/nginx-http-flv-module
    5. 最后执行(先删除之前编译的nginx,我发现有时设置的第3方模块不起作用,删除后再make就可以了):rm -rf output/build/nginx-1.15.7 && make
    6. 这会在Buildroot的dl/nginx目录下自动下载源码,并编译
    7. 结果保存在output/images目录下,有emmc.img, sdcard.img,可以直接烧写到板能的EMMC或SD卡上

rtmp协议走的端口经常被防火墙拦截,可以使用http_flv协议(修改配置文件增加一个location节点) rtmp推流给nginx过程:rtmp推流到某个地址,nginx访问的html界面中含有这个地址,就能观看到摄像头视频;

7.内网穿透:

  1. 可以使用花生壳实现内网穿透只要本地浏览器可以拉流,就可以映射到花生壳,但是免费版带宽只有1Mbps很卡;
  2. 也可以将nginx部署到自己的服务器;

3.基于Linux从零写BootLoader

单板下载方式:

  1. 后台式下载:在升级的时候,新固件在后台悄悄下载,即新固件下载属于应用程序功能的一部分,在新固件下载过程中,应用可以正常使用。下载完成后,系统再跳到BootLoader程序,由BootLoader完成新固件覆盖老固件的操作
  2. 非后台式下载:在升级的时候,系统需要先从应用程序跳入到BootLoader程序,由BootLoader进行新固件下载工作,下载完成后BootLoader继续完成新固件覆盖老固件的操作,至此升级结束。

新旧固件覆盖模式:

  1. 双区模式:双区模式中老固件和新固件在flash中各占一块bank(存储区),对应后台式下载;
  2. 单区模式:单区模式的非后台式下载只有一个bank0(运行区),老固件和新固件共享这一个bank0,对应非后台式下载。

MCU OTA升级过程:

  1. 制作升级包:固件Firmware通过数字签名得到升级包(包括firmware、header和signature value);
  2. 下载升级包:根据上位机软件和MCU设备约定的通信协议,上位机软件将升级包通过OTA方式发送给MCU设备,MCU设备收到数据后,根据通信协议解析出升级包的数据,并将升级包的数据保存到存储器中。
  3. 验签升级包:MCU设备接收完所有的升级包后,先计算升级包中固件的摘要,然后使用非对称秘钥的公钥解密升级包的签名值,如果解密出来的固件摘要与自己计算的摘要相同,则验签成功。
  4. 固件更新:验签成功保证了固件的完整性和合法性后,MCU设备从应用程序进入BootLoader程序,在BootLoader程序中将flash中的新固件数据搬运到旧固件的存储区,将其覆盖。然后BootLoader程序启动固件运行,此时固件为新固件。

Linux OTA升级过程: 升级过程基本和MCU类似,有以下概念:

  1. linux系统主要由三大部分组成为uboot(引导启动程序)、kernel(内核)和rootfs(根文件系统),在flash中以此存放。
  2. Linux系统的启动流程:上电->bootloader->(启动)->kernel->(挂载)->rootfs->(启动)->app;
  3. 一般可在uboot中下载升级包来升级uboot\kernel\rootfs ,与MCU在BootLoader程序中完成升级类似。
  4. 应用程序升级流程:制作升级包(打包签名工具)、下载升级包(下载工具)、升级包验签、程序更新。(与MCU OTA升级的区别:在制作升级包时将应用程序相关的文件,比如可执行程序、库文件、配置文件打包为压缩包再进行签名)
  5. OTA升级的核心:1.如何接收固件;2.如何保证固件的完整性和合法性;3.如何替换固件;

image.png

RAM、ROM、Flash的区别:

  1. RAM:掉电数据丢失,但运行快,正是因为运行快,所以程序中变化的数据都会在RAM中变化,变量也存储在里面。
  2. flash:运行慢,但掉电数据不丢失,正是因为掉电不丢失,所以写好的程序会存在flash里面。
  3. ROM:一种半导体内存,其特性是一旦储存资料就无法再将之改变或删除。通常用在不需经常变更资料的电子或电脑系统中,资料并且不会因为电源关闭而消失。只能读出事先所存数据的固态半导体存储器。
  4. 在单片机中RAM是存变量以及变量的运算的地方,flash是存程序的地方。

BootLoader 引入的目的:更新系统; 主要作用:

  1. 初始化硬件:比如设置时钟、初始化内存;
  2. 启动内核/APP:从Flash读出内存、存入内存、给内核设置参数、启动内核;
  3. 调试作用:在开发产品时,需要经常调试内核,使用BootLoader可以方便地更新内核;

必备知识: 段/重定位 散列文件 异常向量

相关信息

(待完善,后续从0写单片机的bootloader和linux的bootloader,分析现有源码,最后理解linux的uboot)

4.uboot移植

uboot:universal bootloader; uboot就是一个裸机程序,功能是用来启动内核,进而启动各种应用程序;作为通用的bootloader能支持很多soc厂家、板卡厂家、开发的不同型号的板;

linux硬件组成: image.png

在flash中有uboot、内核和文件系统的程序。单片机内存比较小一般64KB,可以直接用SRAM(不用初始化),linux内存比较大,几百M,几G,一般用DDR/SDRAM(需要初始化)

uboot有flash的驱动程序,能读flash,上电启动过程:

  1. 先运行uboot,启动内核:
    1. 初始化内存;
    2. 初始化其他硬件(时钟、flash);
    3. 读flash把内核copy进内存;
    4. 启动内核;
  2. 内核程序启动应用程序:
    1. 读写flash,启动驱动程序:网络/u盘/LCD/其他输入输出设备;
    2. 以一定的格式能读写文件,文件系统;
    3. 找到并启动APP;

uboot源码提供了dtb目录用来各种厂家的设备树指定硬件资源,这些dts文件不会编译进uboot的可执行文件,只作为配置文件传给uboot使用。

烧录的uboot = 原始uboot.bin + 某个dtb;

保证uboot源码不臃肿。

XIP(execute in place),RAM和Flash都是XIP设备,cpu可以直接发出地址信号读得到指令并执行,都是cpu读取指令就好像直接在内存上运行的程序,其实执行是在cpu上。SD卡就不是XIP设备,需要CPU通过emmc控制器读取SD上的指令然后运行(这个过程需要通过BootROM,cpu可以直接读BootROM上代码执行,BootROM上的代码可以将SD卡上的uboot上的程序读到内存中)。

因此uboot启动流程,根据uboot代码位置分为两种情况:

  1. 对于XIP设备:
    1. 硬件初始化(内存、flash、时钟等);
    2. 读flash,把内核copy进内存;
    3. 启动内核;
  2. 对于非XIP设备:
    1. 由BootROM把uboot复制进内存RAM;
    2. 下面开始执行uboot;
    3. 硬件初始化(不用初始化RAM);
    4. 读flash,把内核copy进内存;
    5. 启动内核;

uboot源码结构,在u-boot目录下执行"tree . -d > 1.txt",可以得到目录的结构,精简如下:

shell
├── arch │   ├── arm // 1. 架构相关 │   │   ├── cpu │   │   │   ├── armv7 │   │   │   │   ├── mx6 │   │   ├── dts │   │   │   └── include │   │   │   └── dt-bindings -> ../../../../include/dt-bindings │   │   ├── include │   │   │   ├── asm │   │   │   │   ├── arch-imx │   │   │   │   ├── arch-imx8 │   │   │   │   ├── arch-imx8m │   │   │   │   ├── imx-common │   │   │   └── debug │   │   ├── lib │   │   ├── mach-rockchip │   │   │   ├── rk3036 │   │   │   ├── rk3288 │   │   │   └── rk3399 │   │   ├── lib ├── board // 单板相关 │   ├── freescale │   │   ├── common │   │   │   └── p_corenet │   │   ├── corenet_ds │   │   ├── mx6ul_14x14_ddr3_arm2 │   │   ├── mx6ul_14x14_evk │   │   ├── mx6ul_14x14_lpddr2_arm2 │   │   ├── mx6ull_ddr3_arm2 │   │   ├── mx6ullevk ├── cmd // 通用的命令 │   ├── fastboot │   └── mvebu ├── common // 通用的 │   ├── eeprom │   ├── init │   └── spl ├── configs ├── disk ├── drivers // 各类驱动 ├── fs // 文件系统 │   ├── cbfs │   ├── cramfs │   ├── ext4 │   ├── fat │   ├── jffs2 │   ├── reiserfs │   ├── sandbox │   ├── ubifs │   ├── yaffs2 │   └── zfs ├── include ├── lib // 库 ├── net // 网络协议

Makefile分析:比如all规则,clean规则,include语法;

vim命令删除注释:g/^#/d

uboot中有很多源码,用编译哪些源码得到最后的uboot.bin文件需要配置。比如IMX6ULL配置命令: make mx6ull_14x14_evk_defconfig生成了.config文件。

执行过程:

  • 制作工具:scripts/kconfig/conf
  • 把默认配置信息写入文件".config"

文件.config中含有架构、soc、厂家、单板等配置信息。

以命令mx6ull_14x14_evk_defconfig为例分析怎么得到的.config文件:

从makefile中分析过程,mx6ull_14x14_evk_defconfig依赖scripts/kconfig/conf,又依赖其他东西...,makefile中以下过程展开:

shell
mx6ull_14x14_evk_defconfig: scripts/kconfig/conf $(Q)$< $(silent) --defconfig=arch/$(SRCARCH)/configs/$@ $(Kconfig)

就是:

shell
UBOOTVERSION=2017.03 scripts/kconfig/conf --defconfig=arch/../configs/mx6ull_14x14_evk_defconfig Kconfig

所以要分析scripts/kconfig/conf.c,该代码整体结构:

shell
defconfig_file = "arch/../configs/mx6ull_14x14_evk_defconfig"; name = "Kconfig" conf_parse(name); // 解析uboot根目录下的Kconfig文件 conf_read(defconfig_file); // 读配置文件 conf_set_all_new_symbols(def_default); // 设置new_symbols为默认值 conf_write(NULL); // 写到.config

分析结果:

  • Kconfig:这是一个通用文件,里面规定了一些依赖,比如:
    • 如果是ARM架构,就默认选中A、B、C配置
    • 如果是RISC-V架构,就默认选中a、b、c配置
  • defconfig_file:这是厂家提供的,里面定义了
    • ARM架构
    • 自己的一些配置项
  • 怎么处理呢?
    • 使用defconfig_file的内容去解析Kconfig,确定各个依赖的配置项
    • 其他未涉及的配置项,给它们指定默认值
    • 写入.config

uboot config界面语法(增加配置项、菜单(多选、单选),选择了某个配置项后就会把这个功能编进uboot里);

得到的.config用来选择哪些目录/文件被编译,并得到一个.h文件存放.c文件用到的宏等;

得到.config后,使用make就可以编译uboot了,内部过程:

  1. 检查更新头文件,比如include/config.h、u-boot.cfg(才是完全的最终的信息)
  2. 制作工具
  3. 交叉编译:编译哪些目录哪些文件,.c文件可能需要使用.config的配置值,它可以引用config.h

整体理解uboot编译流程(涉及子文件目录的编译等等,Makefile文件还是很复杂的)

XIP

BootROM

相对寻址、绝对寻址

uboot完整启动流程总结:

本文作者:zzw

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 License 许可协议。转载请注明出处!