核心目标:衔接前 28 天嵌入式基础,聚焦嵌入式 Linux 核心实战(贴合工业 / AIoT 工作场景),掌握 “交叉编译、根文件系统构建、嵌入式 Linux 应用开发、简单驱动入门”,解决 “裸机 / RTOS 过渡到 Linux” 的适配问题,理解嵌入式 Linux 与传统 RTOS 的差异,实现 “嵌入式全栈 + Linux” 的能力闭环 —— 毕竟实际工作中,工业网关、边缘计算节点、智能终端等主流场景,均以嵌入式 Linux 为核心。
一、核心定位:嵌入式 Linux vs 裸机 / RTOS vs 桌面 Linux
首先明确嵌入式 Linux 的核心场景和差异,避免混淆:
| 对比维度 | 嵌入式 Linux | 裸机 / FreeRTOS | 桌面 Linux(Ubuntu) |
|---|---|---|---|
| 核心优势 | 多任务、丰富协议栈(TCP/IP/MQTT)、支持复杂应用(AI 推理、GUI) | 实时性强(≤1ms)、资源占用少(RAM≥8KB) | 图形化友好、软件生态丰富、无需交叉编译 |
| 硬件要求 | 至少 ARM9 级以上(RAM≥64MB,Flash≥128MB) | 微控制器(RAM≥8KB,Flash≥32KB) | x86/x86_64 架构(资源充足) |
| 典型场景 | 工业网关、边缘计算节点、智能机顶盒 | 传感器节点、电机控制器、低功耗终端 | 办公电脑、服务器 |
| 开发重点 | 交叉编译、根文件系统、驱动适配、应用部署 | 外设驱动、RTOS 调度、低功耗优化 | 软件安装、图形化应用开发 |
核心结论:嵌入式 Linux 是 “复杂嵌入式系统” 的首选 —— 当需要运行多进程、复杂协议(如 HTTP/HTTPS)、AI 模型、GUI 时,比 RTOS 更高效;但实时性要求极高(如≤1ms)的场景(如电机精密控制),仍需 RTOS 或 “Linux+RTOS 异构”(如 STM32MP1 的 A7 核跑 Linux,M4 核跑 FreeRTOS)。
二、嵌入式 Linux 开发环境搭建(40 分钟)
嵌入式 Linux 开发的核心是 “交叉编译”(在 x86 桌面机编译 ARM/Linux 平台的程序),以STM32MP157(ARM Cortex-A7 双核,支持 Linux+FreeRTOS 异构) 为例,搭建完整开发环境:
(一)核心工具清单
- 交叉编译工具链:ARM 官方工具链(arm-linux-gnueabihf-gcc),用于编译 ARM 架构程序;
- 根文件系统(RootFS):Buildroot 构建(轻量级、可定制,适合嵌入式);
- 开发板:STM32MP157 开发板(自带 Linux 镜像,支持 SD 卡启动);
- 调试工具:SecureCRT(串口终端)、SSH(远程登录)、ADB(设备调试);
- 编译构建工具:Makefile、CMake(管理复杂项目)。
(二)实操:环境搭建步骤
1. 安装交叉编译工具链
bash
# 1. 下载工具链(ARM官方,支持ARM Cortex-A系列)
wget https://developer.arm.com/-/media/Files/downloads/gnu-a/10.3-2021.07/binrel/gcc-arm-10.3-2021.07-x86_64-arm-linux-gnueabihf.tar.xz
# 2. 解压到/usr/local/
sudo tar -xvf gcc-arm-10.3-2021.07-x86_64-arm-linux-gnueabihf.tar.xz -C /usr/local/
# 3. 配置环境变量(~/.bashrc末尾添加)
echo "export PATH=/usr/local/gcc-arm-10.3-2021.07-x86_64-arm-linux-gnueabihf/bin:\$PATH" >> ~/.bashrc
source ~/.bashrc
# 4. 验证(出现版本信息则成功)
arm-linux-gnueabihf-gcc -v
2. 用 Buildroot 构建根文件系统
Buildroot 是嵌入式 Linux 的根文件系统构建工具,可定制内核、驱动、软件包(如 MQTT 客户端、Python):
bash
# 1. 下载Buildroot(2023.02稳定版)
wget https://buildroot.org/downloads/buildroot-2023.02.tar.gz
tar -xvf buildroot-2023.02.tar.gz
cd buildroot-2023.02
# 2. 配置Buildroot(针对STM32MP157)
make stm32mp157a_dk1_defconfig # 官方默认配置(适配STM32MP1开发板)
make menuconfig # 图形化配置(可选,如添加MQTT客户端、Python3)
# 3. 编译(耗时1-2小时,依赖网络下载软件包)
make -j4 # 4线程编译
# 4. 生成产物(输出目录:output/images/)
# - rootfs.tar:根文件系统压缩包
# - zImage:Linux内核镜像
# - stm32mp157a-dk1.dtb:设备树文件
3. 开发板烧录与启动
- 将 Buildroot 生成的
zImage、stm32mp157a-dk1.dtb、rootfs.tar烧录到 SD 卡(用 Etcher 工具); - 开发板插 SD 卡,上电,通过 SecureCRT 连接串口(波特率 115200),启动后进入 Linux 终端:
bash
# 成功启动后,终端显示Linux登录提示符(默认用户名root,无密码) root@stm32mp1:~#
三、嵌入式 Linux 应用开发实战(50 分钟)
基于 STM32MP157 Linux 平台,开发 “温湿度采集 + MQTT 上传” 应用 —— 复用之前的 AHT10 传感器驱动,移植到 Linux 环境(重点是 “用户态应用开发”,区别于裸机驱动)。
(一)核心差异:Linux 用户态 vs 裸机 / RTOS
- 裸机 / RTOS:直接操作寄存器、中断;
- Linux 用户态:通过 “文件系统接口” 操作外设(如 I2C 设备映射为
/dev/i2c-1,通过读写文件操作 I2C),无需直接操作寄存器。
(二)实操 1:AHT10 传感器应用开发(用户态)
1. 硬件准备:AHT10 接入 STM32MP1 I2C1
- AHT10 SCL → STM32MP1 PB8(I2C1_SCL);
- AHT10 SDA → STM32MP1 PB9(I2C1_SDA);
- Linux 下 I2C 设备节点:
/dev/i2c-1(由设备树配置)。
2. 应用代码(aht10_linux.c)
c
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/i2c-dev.h>
#define AHT10_ADDR 0x38 // AHT10 I2C地址
#define I2C_DEV_PATH "/dev/i2c-1" // Linux I2C设备节点
// 初始化AHT10
int aht10_init(int fd) {
uint8_t init_cmd[3] = {0xE1, 0x00, 0x00};
// 设置I2C从设备地址
if (ioctl(fd, I2C_SLAVE, AHT10_ADDR) < 0) {
perror("ioctl set slave addr failed");
return -1;
}
// 发送初始化命令
if (write(fd, init_cmd, 3) != 3) {
perror("write init cmd failed");
return -1;
}
usleep(10000); // 等待初始化完成
return 0;
}
// 读取温湿度
int aht10_read(int fd, float *temp, float *humi) {
uint8_t measure_cmd[3] = {0xAC, 0x33, 0x00};
uint8_t recv_buf[6] = {0};
if (ioctl(fd, I2C_SLAVE, AHT10_ADDR) < 0) {
perror("ioctl set slave addr failed");
return -1;
}
// 发送测量命令
if (write(fd, measure_cmd, 3) != 3) {
perror("write measure cmd failed");
return -1;
}
usleep(80000); // 等待测量完成(80ms)
// 读取数据
if (read(fd, recv_buf, 6) != 6) {
perror("read data failed");
return -1;
}
// 解析数据(与裸机逻辑一致)
uint32_t humi_raw = (recv_buf[1] << 12) | (recv_buf[2] << 4) | ((recv_buf[3] & 0xF0) >> 4);
*humi = (humi_raw / 1048576.0) * 100.0;
uint32_t temp_raw = ((recv_buf[3] & 0x0F) << 16) | (recv_buf[4] << 8) | recv_buf[5];
*temp = (temp_raw / 1048576.0) * 200.0 - 50.0;
return 0;
}
int main() {
int fd;
float temp, humi;
// 1. 打开I2C设备(Linux文件系统接口)
fd = open(I2C_DEV_PATH, O_RDWR);
if (fd < 0) {
perror("open i2c dev failed");
return -1;
}
// 2. 初始化AHT10
if (aht10_init(fd) < 0) {
close(fd);
return -1;
}
// 3. 循环读取并打印
while (1) {
if (aht10_read(fd, &temp, &humi) == 0) {
printf("Temperature: %.1f℃, Humidity: %.1f%%RH\n", temp, humi);
} else {
printf("read aht10 data failed\n");
}
sleep(1); // 1秒读取一次
}
close(fd);
return 0;
}
3. 交叉编译与部署
bash
# 1. 交叉编译(x86桌面机编译ARM/Linux程序)
arm-linux-gnueabihf-gcc aht10_linux.c -o aht10_app -static # -static:静态编译(避免依赖根文件系统库)
# 2. 部署到开发板(两种方式)
# 方式1:SD卡拷贝(将aht10_app复制到SD卡,插入开发板挂载后运行)
# 方式2:SSH传输(开发板联网后,通过scp传输)
scp aht10_app root@192.168.1.100:/root/ # 192.168.1.100是开发板IP
# 3. 开发板运行
root@stm32mp1:~# ./aht10_app # 输出温湿度数据
Temperature: 25.3℃, Humidity: 48.5%RH
Temperature: 25.4℃, Humidity: 48.3%RH
(三)实操 2:MQTT 上传功能整合(Linux 用户态)
复用之前的 MQTT 协议,使用 Linux 下的开源 MQTT 客户端库paho.mqtt.c:
1. Buildroot 添加 MQTT 库(编译根文件系统时)
bash
make menuconfig → 搜索"paho-mqtt" → 勾选"paho-mqtt-c" → 重新编译Buildroot(make -j4)
2. 整合 MQTT 的应用代码(核心片段)
c
#include <MQTTClient.h>
#define MQTT_BROKER "tcp://192.168.1.200:1883" // 阿里云/本地MQTT服务器
#define MQTT_CLIENT_ID "stm32mp1_aht10"
#define MQTT_TOPIC "embedded/linux/data"
// MQTT初始化
MQTTClient connect_mqtt() {
MQTTClient client;
MQTTClient_create(&client, MQTT_BROKER, MQTT_CLIENT_ID, MQTTCLIENT_PERSISTENCE_NONE, NULL);
MQTTClient_connectOptions conn_opts = MQTTClient_connectOptions_initializer;
conn_opts.keepAliveInterval = 60;
conn_opts.cleansession = 1;
if (MQTTClient_connect(client, &conn_opts) != MQTTCLIENT_SUCCESS) {
printf("mqtt connect failed\n");
return NULL;
}
return client;
}
// 主函数中添加MQTT上传逻辑
int main() {
// ... 之前的AHT10初始化代码 ...
MQTTClient mqtt_client = connect_mqtt();
char payload[64];
while (1) {
if (aht10_read(fd, &temp, &humi) == 0) {
// 构造MQTT消息
sprintf(payload, "{\"temp\":%.1f,\"humi\":%.1f}", temp, humi);
MQTTClient_message pub_msg = MQTTClient_message_initializer;
pub_msg.payload = payload;
pub_msg.payloadlen = strlen(payload);
pub_msg.qos = 1;
// 发布消息
MQTTClient_publishMessage(mqtt_client, MQTT_TOPIC, &pub_msg, NULL);
printf("publish: %s\n", payload);
}
sleep(1);
}
// ... 关闭资源 ...
}
3. 交叉编译(链接 MQTT 库)
bash
arm-linux-gnueabihf-gcc aht10_mqtt.c -o aht10_mqtt_app -static -lpaho-mqtt3c
# -lpaho-mqtt3c:链接MQTT客户端库(Buildroot编译后已包含在交叉编译工具链中)
四、嵌入式 Linux 驱动开发入门(30 分钟)
嵌入式 Linux 中,外设驱动分为 “内核态驱动” 和 “用户态驱动”:
- 用户态驱动:如上面的 AHT10 应用,通过文件系统接口(/dev/i2c-1)操作,适合简单外设;
- 内核态驱动:编译进 Linux 内核或作为模块加载,适合复杂外设(如 SPI、CAN、LCD)、需要中断的场景,实时性比用户态高。
(一)核心概念:Linux 字符设备驱动
最基础的内核驱动类型(如 LED、按键、简单传感器),核心是 “注册字符设备→实现文件操作接口(open/read/write/ioctl)→ 创建设备节点”。
(二)实操:LED 内核驱动开发(STM32MP1 LED)
1. 硬件:LED 接入 STM32MP1 PA5
- LED 正极 → PA5(通过 220Ω 电阻),负极 → GND;
- Linux 设备树中配置 PA5 为输出引脚。
2. LED 驱动代码(led_drv.c,内核模块形式)
c
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/gpio.h>
#include <linux/platform_device.h>
#include <linux/of_gpio.h>
#define LED_DEV_NAME "stm32mp1_led" // 设备名称
#define LED_CLASS_NAME "stm32mp1_led_class" // 设备类名称
// 驱动私有数据
struct led_dev {
dev_t dev_num; // 设备号
struct cdev cdev; // 字符设备
struct class *class; // 设备类
struct device *device; // 设备
int led_gpio; // LED GPIO引脚号
};
struct led_dev led_dev;
// 打开设备(应用层调用open时触发)
static int led_open(struct inode *inode, struct file *file) {
file->private_data = &led_dev; // 关联私有数据
return 0;
}
// 写设备(应用层调用write时触发,控制LED亮灭)
static ssize_t led_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) {
struct led_dev *dev = file->private_data;
char val;
// 从用户空间拷贝数据(用户态→内核态)
if (copy_from_user(&val, buf, 1) != 0) {
return -EFAULT;
}
// 控制GPIO(亮:1,灭:0)
gpio_set_value(dev->led_gpio, val - '0');
return count;
}
// 文件操作接口
static const struct file_operations led_fops = {
.owner = THIS_MODULE,
.open = led_open,
.write = led_write,
};
// 驱动probe函数(设备匹配时执行)
static int led_probe(struct platform_device *pdev) {
struct device_node *node = pdev->dev.of_node;
int ret;
// 1. 从设备树获取GPIO引脚号
led_dev.led_gpio = of_get_named_gpio_flags(node, "led-gpios", 0, NULL);
if (!gpio_is_valid(led_dev.led_gpio)) {
dev_err(&pdev->dev, "invalid gpio pin\n");
return -EINVAL;
}
// 2. 请求GPIO并设置为输出
ret = gpio_request(led_dev.led_gpio, "led_gpio");
if (ret < 0) {
dev_err(&pdev->dev, "gpio request failed\n");
return ret;
}
gpio_direction_output(led_dev.led_gpio, 0); // 初始灭
// 3. 分配设备号
ret = alloc_chrdev_region(&led_dev.dev_num, 0, 1, LED_DEV_NAME);
if (ret < 0) {
dev_err(&pdev->dev, "alloc dev num failed\n");
goto err_gpio_free;
}
// 4. 初始化字符设备
cdev_init(&led_dev.cdev, &led_fops);
led_dev.cdev.owner = THIS_MODULE;
ret = cdev_add(&led_dev.cdev, led_dev.dev_num, 1);
if (ret < 0) {
dev_err(&pdev->dev, "cdev add failed\n");
goto err_unregister_chrdev;
}
// 5. 创建设备类和设备节点(/dev/stm32mp1_led)
led_dev.class = class_create(THIS_MODULE, LED_CLASS_NAME);
if (IS_ERR(led_dev.class)) {
ret = PTR_ERR(led_dev.class);
dev_err(&pdev->dev, "class create failed\n");
goto err_cdev_del;
}
led_dev.device = device_create(led_dev.class, &pdev->dev, led_dev.dev_num, NULL, LED_DEV_NAME);
if (IS_ERR(led_dev.device)) {
ret = PTR_ERR(led_dev.device);
dev_err(&pdev->dev, "device create failed\n");
goto err_class_destroy;
}
dev_info(&pdev->dev, "led driver probe success\n");
return 0;
// 错误处理流程
err_class_destroy:
class_destroy(led_dev.class);
err_cdev_del:
cdev_del(&led_dev.cdev);
err_unregister_chrdev:
unregister_chrdev_region(led_dev.dev_num, 1);
err_gpio_free:
gpio_free(led_dev.led_gpio);
return ret;
}
// 设备树匹配表(与设备树中的节点匹配)
static const struct of_device_id led_of_match[] = {
{ .compatible = "stm32mp1,led" },
{ /* Sentinel */ }
};
MODULE_DEVICE_TABLE(of, led_of_match);
// 平台驱动结构体
static struct platform_driver led_driver = {
.probe = led_probe,
.driver = {
.name = LED_DEV_NAME,
.of_match_table = led_of_match,
},
};
// 模块加载(insmod时执行)
module_platform_driver(led_driver);
MODULE_LICENSE("GPL"); // 必须声明GPL协议
MODULE_DESCRIPTION("STM32MP1 LED Kernel Driver");
MODULE_AUTHOR("Embedded Linux Developer");
3. 驱动编译(Makefile)
makefile
# 交叉编译内核模块的Makefile
KERNELDIR ?= /home/user/buildroot-2023.02/output/build/linux-5.15.100 # Buildroot编译的Linux内核目录
PWD := $(shell pwd)
obj-m += led_drv.o # 编译为模块
all:
$(MAKE) -C $(KERNELDIR) M=$(PWD) ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(PWD) ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- clean
4. 驱动部署与测试
bash
# 1. 交叉编译驱动模块
make # 生成led_drv.ko
# 2. 部署到开发板
scp led_drv.ko root@192.168.1.100:/root/
# 3. 开发板加载驱动
root@stm32mp1:~# insmod led_drv.ko # 加载模块
root@stm32mp1:~# lsmod # 查看加载的模块
root@stm32mp1:~# ls /dev/stm32mp1_led # 查看设备节点(成功则存在)
# 4. 测试驱动(控制LED亮灭)
root@stm32mp1:~# echo "1" > /dev/stm32mp1_led # LED亮
root@stm32mp1:~# echo "0" > /dev/stm32mp1_led # LED灭
五、嵌入式 Linux 与前序知识的整合(20 分钟)
(一)FreeRTOS vs Linux 多任务对比
| 特性 | FreeRTOS | 嵌入式 Linux |
|---|---|---|
| 任务调度 | 抢占式 + 时间片轮转,支持优先级继承 | 基于 CFS 调度器(完全公平调度),支持多进程 / 线程 |
| 任务类型 | 线程(无进程概念) | 进程(独立地址空间)+ 线程(共享地址空间) |
| 通信方式 | 信号量、队列、事件标志组 | 管道、消息队列、共享内存、信号、Socket |
| 实时性 | 高(≤1ms) | 中等(默认≥10ms,可通过内核裁剪优化至 1ms) |
| 适用场景 | 低功耗、实时控制(电机、传感器) | 复杂应用、多协议、AI 推理、GUI |
(二)协议移植:Modbus RTOS→Linux
之前的 Modbus 协议代码(RTOS 版)可直接移植到 Linux,只需修改:
- 串口操作:从 RTOS 的串口驱动→Linux 的
/dev/ttySTM0文件操作; - 延时函数:
vTaskDelay(100)→usleep(100000); - 多任务:RTOS 任务→Linux 线程(pthread 库)。
六、第二十九天必掌握的 3 个核心点
- 嵌入式 Linux 开发流程:会搭建交叉编译环境、用 Buildroot 构建根文件系统、将应用程序部署到开发板;
- 用户态应用开发:理解 Linux 文件系统接口,能开发传感器(如 AHT10)、MQTT 上传等用户态应用;
- 内核驱动入门:掌握字符设备驱动的核心流程(注册设备→实现文件操作→创建设备节点),能编译加载简单内核模块。
总结
第 29 天开启了 “嵌入式 + Linux” 的实战篇章,核心是理解 “嵌入式 Linux 的差异化定位”—— 它不是替代 RTOS,而是互补:简单实时场景用 RTOS,复杂多协议 / 多应用场景用 Linux,异构架构(如 STM32MP1)可实现 “Linux+RTOS” 协同。
嵌入式 Linux 是工业网关、边缘计算、智能终端等高薪场景的必备技能,后续将深入内核裁剪、设备树配置、工业总线(CAN/EtherCAT)Linux 驱动、边缘 AI 模型部署(TensorFlow Lite Linux 版)等实战内容。

3165

被折叠的 条评论
为什么被折叠?



