嵌入式进阶入门:从模块开发到系统设计

完成嵌入式初级应用开发后,进阶阶段的核心是建立 “系统思维”—— 突破单一传感器或执行器的控制局限,掌握多模块协同、实时任务调度、进阶通信协议及工程化开发方法。本文将以 STM32 与 FreeRTOS 为核心,通过 “多参数环境控制器”“串口设备组网” 两大综合项目,拆解嵌入式进阶开发的关键技术与实践思路。

一、进阶基础:从裸机到实时操作系统(FreeRTOS)

初级开发多采用裸机编程(单循环执行),无法高效处理多任务场景(如同时采集数据、响应按键、执行控制)。实时操作系统(RTOS)通过任务调度实现多任务并发执行,是嵌入式进阶的必备技能,FreeRTOS 因轻量、开源、易用成为主流选择。

1. FreeRTOS 核心概念与环境搭建

(1)核心术语解析

术语作用说明类比场景

任务(Task)独立的执行单元,包含程序代码与运行状态电脑中的 “进程”,如浏览器、文档编辑器

任务调度器按优先级分配 CPU 资源,实现多任务并发交通指挥中心,调度车辆通行

队列(Queue)任务间数据传递的 “缓冲区”,解耦任务依赖办公室的 “文件传递箱”

信号量(Semaphore)控制共享资源访问,避免冲突公共电话亭的 “占用标识”

定时器(Timer)触发定时任务,替代裸机中的delay()厨房的 “定时闹钟”

(2)STM32+FreeRTOS 环境搭建

以 STM32F103C8T6 为例,基于 STM32CubeMX 快速搭建 FreeRTOS 开发环境:

CubeMX 配置步骤

新建项目,选择芯片后配置系统时钟为 72MHz;

点击 “Middleware → FreeRTOS”,选择 “CMSIS_V1” 接口(兼容性好);

在 “Tasks and Queues” 中创建任务(如Task_Sensor采集数据、Task_Control执行控制);

配置所需外设(I2C、UART、GPIO),生成 Keil MDK 工程。

核心 API 入门

// 创建任务(CubeMX自动生成,也可手动创建)

xTaskCreate(Task_Sensor, "SensorTask", 128, NULL, 2, &xHandleSensor);

// 任务函数(必须为void类型,无返回值,参数为void*)

void Task_  heluona.jielida168.cn  Sensor(void const * argument) {

  for(;;) {

    // 任务逻辑:读取传感器数据

    ReadSensorData();

    vTaskDelay(1000); // 任务延时1秒(释放CPU给其他任务)

  }

}

// 队列发送数据

uint8_t data[4] = {0x01, 0x02, 0x03, 0x04};

xQueueSend(xQueue_Data, &data, portMAX_DELAY);

// 队列接收数据

uint8_t recvData[4];

if(xQueueReceive madeli.jielida168.cn  (xQueue_Data, &recvData, 100) == pdPASS) {

  // 处理接收的数据

}

2. 裸机 vs FreeRTOS:多任务处理对比

以 “同时采集温湿度、响应按键、控制 LED” 为例,两种开发模式的实现差异显著:

开发模式实现方式缺点

裸机编程单循环中轮询所有任务,用delay()延时高优先级任务(如按键响应)会被低优先级任务阻塞

FreeRTOS创建 3 个独立任务,调度器按优先级分配 CPU 资源需理解任务调度机制,内存占用略增

二、进阶实战一:多参数环境控制器(FreeRTOS 多任务协同)

本项目整合温湿度(SHT30)、光照(BH1750)、继电器(控制风扇 / 灯)、LCD1602 显示屏,通过 FreeRTOS 实现 “数据采集→显示→自动控制” 的全流程,模拟智能家居环境控制场景。

1. 系统架构与硬件选型

(1)功能架构图

┌─────────────┐      ┌─────────────┐      ┌─────────────┐

│  感知层    │      │  控制层    │      │  执行层    │

│  - SHT30    │→────►│  - FreeRTOS │→────►│  - 继电器1  │(风扇)

│  - BH1750  │      │    任务调度 │      │  - 继电器2  │(灯)

│  - 按键    │      │  - 队列通信 │      │  - LCD1602  │(显示)

└─────────────┘      └─────────────┘      └─────────────┘

(2)硬件清单(基于 STM32)

硬件模块型号规格作用连接引脚

主控开发板STM32F103C8T6核心控制与任务调度-

温湿度传感器SHT30(I2C)采集温湿度数据PB7(SDA)、PB6(SCL)

光照传感器BH1750(I2C)采集光照强度PB7(SDA)、PB6(SCL)

显示模块LCD1602(并行接口)显示环境数据与设备状态PA0-PA7、PB0-PB1

执行器继电器模块 ×2控制风扇(湿度)、灯(光照)PC13、PC14

输入设备按键 ×2手动切换自动 / 手动模式PA1、PA2

2. FreeRTOS 任务设计与通信机制

(1)任务划分(按优先级从高到低)

任务名称优先级核心功能执行周期

Task_Key3按键扫描与模式切换(高优先级,及时响应)100ms

Task_Control2数据处理与继电器控制(核心逻辑)500ms

Task_Sensor1传感器数据采集(低优先级,可延时)1000ms

Task_Display1LCD 数据刷新(低优先级,非实时)500ms

(2)通信设计:队列传递数据

创建xQueue_Sensor队列:Task_Sensor将采集的温湿度、光照数据发送到队列;

创建xQueue_Mode队列:Task_Key将模式切换指令(自动 / 手动)发送到Task_Control。

3. 核心代码实现

(1)数据结构定义

// 传感器数据结构体

typedef struct {

  float temperature; // 温度(℃)

  float humidity;    // 湿度(%RH)

  uint16_t lux;  guojimilan.jielida168.cn     // 光照(lx)

} SensorData_t;

// 模式控制指令

typedef enum {

  MODE_AUTO = 0,    // 自动模式

  MODE_MANUAL        // 手动模式

} ControlMode_t;

(2)任务实现(main.c 核心部分)

#include "main.h"

#include "i2c.h"

#include "gpio.h"

#include "lcd1602.h"

#include "FreeRTOS.h"

#include "task.h"

#include "queue.h"

// 队列句柄

QueueHandle_t xQueue_Sensor;

QueueHandle_t xQueue_Mode;

// 任务声明

void Task_Sensor acmilan.jielida168.cn (void const * argument);

void Task_Control(void const * argument);

void Task_Key(void const * argument);

void Task_Display(void const * argument);

int main(void) {

  HAL_Init();

  SystemClock_Config();

  MX_GPIO_Init();

  MX_I2C1_Init();


  // 初始化外设

  LCD1602_Init();

  SHT30_Init();

  BH1750_Init();

  // 创建队列(传感器数据队列:10个元素;模式队列:1个元素)

  xQueue_Sensor =  meiyinci.jielida168.cn xQueueCreate(10, sizeof(SensorData_t));

  xQueue_Mode = xQueueCreate(1, sizeof(ControlMode_t));

  // 创建任务

  xTaskCreate(Task_Sensor,  "SensorTask",  128, NULL, 1, NULL);

  xTaskCreate(Task_Control, "ControlTask", 128, NULL, 2, NULL);

  xTaskCreate(Task_Key,    "KeyTask",    128, NULL, 3, NULL);

  xTaskCreate(Task_Display, "DisplayTask", 128, NULL, 1, NULL);

  // 启动任务调度器

  vTaskStartScheduler();

  // 若调度器启动失败,进入死循环

  while(1) {

    Error_Handler();

  }

}

// 传感器采集任务

void Task_Sensor(void const * argument) {

  SensorData_t data;

  for(;;) {

    // 读取传感器数据

    data.temperature = SHT30_ReadTemp();

    data.humidity = SHT30_ReadHumi();

    data.lux = BH1750_ReadLux();

    // 发送数据到队列(超时时间0,立即返回)

    xQueueSend(xQueue_Sensor, &data, 0);

    vTaskDelay(1000); // 1秒采集一次

  }

}

// 控制任务

void Task_Control(void const * argument) {

  SensorData_t recvData;

  ControlMode_t mode = MODE_AUTO;

  for(;;) {

    // 检查是否有模式切换指令

    if(xQueueReceive(xQueue_Mode, &mode, 100) == pdPASS) {

      // 模式切换时更新LCD显示

      LCD1602_SetCursor(0, 1);

      LCD1602_Print(mode == MODE_AUTO ? "Mode:AUTO " : "Mode:MANUAL");

    }

    // 自动模式下执行控制逻辑

    if(mode == MODE_AUTO) {

      // 接收传感器数据(超时100ms)

      if(xQueueReceive(xQueue_Sensor, &recvData, 100) == pdPASS) {

        // 湿度>70%,打开风扇(继电器1吸合)

        if(recvData.humidity > 70.0) {

          HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET);

        } else {

          HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET);

        }

        // 光照<150lx,打开灯(继电器2吸合)

        if(recvData.lux < 150) {

          HAL_GPIO_WritePin(GPIOC, GPIO_PIN_14, GPIO_PIN_RESET);

        } else {

          HAL_GPIO_WritePin(GPIOC, GPIO_PIN_14, GPIO_PIN_SET);

        }

      }

    }

    vTaskDelay(500);

  }

}

// 按键任务

void Task_Key(void const * argument) {

  ControlMode_t mode = MODE_AUTO;

  uint8_t key1State = 1, key2State = 1; // 按键初始状态(上拉高电平)

  for(;;) {

    // 按键1:切换自动/手动模式(消抖处理)

    if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET) {

      HAL_Delay(10);

      if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET && key1State == 1) {

        mode = (mode == MODE_AUTO) ? MODE_MANUAL : MODE_AUTO;

        xQueueSend(xQueue_Mode, &mode, portMAX_DELAY); // 发送模式指令

        key1State = 0;

      }

    } else {

      key1State = 1;

    }

    // 按键2:手动控制继电器(仅手动模式有效)

    if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_2) == GPIO_PIN_RESET) {

      HAL_Delay(10);

      if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_2) == GPIO_PIN_RESET && key2State == 1) {

        if(mode == MODE_MANUAL) {

          // 切换继电器2状态(灯)

          HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_14);

        }

        key2State = 0;

      }

    } else {

      key2State = 1;

    }

    vTaskDelay(100);

  }

}

// 显示任务

void Task_Display(void const * argument) {

  SensorData_t recvData;

  char buf[16];

  LCD1602_Print("Temp:    Hum:  ");

  LCD1602_SetCursor(0, 1);

  LCD1602_Print("Lux:    Mode:AUTO");

  for(;;) {

    if(xQueueReceive(xQueue_Sensor, &recvData, 500) == pdPASS) {

      // 显示温度

      sprintf(buf, "%.1fC", recvData.temperature);

      LCD1602_SetCursor(5, 0);

      LCD1602_Print(buf);

      // 显示湿度

      sprintf(buf, "%.1f%%", recvData.humidity);

      LCD1602_SetCursor(12, 0);

      LCD1602_Print(buf);

      // 显示光照

      sprintf(buf, "%d", recvData.lux);

      LCD1602_SetCursor(4, 1);

      LCD1602_Print(buf);

    }

    vTaskDelay(500);

  }

}

4. 调试与优化要点

任务栈大小调整:若任务执行异常,可能是栈溢出,需在 CubeMX 中增大任务栈(如从 128 改为 256 字);

优先级冲突解决:确保按键任务优先级高于控制任务,避免按键响应延迟;

队列溢出防护:发送数据时设置合理超时时间,避免队列满导致数据丢失。

三、进阶实战二:串口设备组网(Modbus 协议应用)

初级通信多为点对点的简单数据传输,进阶阶段需掌握标准化通信协议实现多设备组网。Modbus 是工业领域最常用的串行通信协议,适用于传感器、控制器等设备的组网通信,本项目实现 “多个传感器节点→主控制器” 的 Modbus RTU 组网。

1. Modbus RTU 协议基础

(1)核心概念

主从架构:1 个主设备(如 STM32 控制器),多个从设备(如温湿度传感器节点),仅主设备可主动发起通信;

数据帧格式

从站地址(1B) + 功能码(1B) + 数据(nB) + 校验码(2B, CRC16)

常用功能码

0x03:读取保持寄存器(从设备存储的测量数据);

0x06:写入单个保持寄存器(控制从设备参数)。

(2)组网架构

┌─────────────┐      ┌─────────────┐      ┌─────────────┐

│  主设备    │      │  从设备1    │      │  从设备2    │

│  STM32F103  │◄────►│  Arduino Uno│◄────►│  Arduino Uno│

│  Modbus主站 │      │  Modbus从站 │      │  Modbus从站 │

│  (采集+控制)│      │  (温湿度) │      │  (光照)  │

└─────────────┘      └─────────────┘      └─────────────┘

2. 硬件与软件实现

(1)硬件准备

主设备:STM32F103C8T

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容