第42章 嵌入式系统编程

42.1 嵌入式系统概述

42.1.1 嵌入式系统的定义

嵌入式系统是一种专用计算机系统,通常嵌入到更大的设备或系统中,用于执行特定的功能。与通用计算机不同,嵌入式系统通常具有以下特点:

  • 专用性:针对特定应用设计
  • 实时性:许多嵌入式系统需要实时响应
  • 资源受限:内存、处理器能力、存储空间有限
  • 可靠性:要求高可靠性和稳定性
  • 功耗约束:许多嵌入式系统由电池供电
  • 体积小巧:通常需要集成到小型设备中

42.1.2 嵌入式系统的应用领域

  • 消费电子:智能手机、平板电脑、智能手表、数字相机、智能电视
  • 工业控制:PLC、工业机器人、传感器网络、过程控制系统
  • 汽车电子:发动机控制系统、ABS、导航系统、娱乐系统、自动驾驶
  • 医疗设备:监护仪、血糖仪、MRI设备、起搏器
  • 航空航天:飞行控制系统、导航系统、卫星设备、导弹制导系统
  • 智能家居:智能灯具、温控器、安防系统、智能门锁
  • 物联网设备:各种传感器节点、智能网关

42.1.3 嵌入式系统的组成

  • 硬件:处理器、内存、存储、外设接口、电源管理
  • 软件:系统软件(RTOS、驱动程序)、应用软件
  • 固件:存储在非易失性存储器中的软件

42.2 嵌入式处理器

42.2.1 嵌入式处理器类型

  • 微控制器(MCU):集成了处理器、内存、I/O接口的单芯片系统

    • 代表:ARM Cortex-M系列、AVR、PIC、MSP430
    • 特点:低功耗、低成本、集成度高
  • 数字信号处理器(DSP):专为数字信号处理设计

    • 代表:TI C2000系列、ADI SHARC系列、Freescale DSP56800
    • 特点:单指令多数据(SIMD)、硬件乘法器
  • 片上系统(SoC):集成了处理器、内存、外设的复杂系统

    • 代表:ARM Cortex-A系列、NXP i.MX系列、TI OMAP系列
    • 特点:高性能、功能丰富、集成度高
  • 现场可编程门阵列(FPGA):可编程逻辑器件

    • 代表:Xilinx Artix-7、Altera Cyclone V
    • 特点:可定制硬件功能、并行处理能力强
  • 专用集成电路(ASIC):为特定应用设计的集成电路

    • 特点:高性能、低功耗、成本高(设计和制造成本)

42.2.2 主流嵌入式处理器架构

  • ARM架构

    • Cortex-M系列:低功耗、实时控制
      • Cortex-M0/M0+:超低功耗,适合简单应用
      • Cortex-M3:中性能,广泛用于工业控制
      • Cortex-M4:带FPU,适合数字信号处理
      • Cortex-M7:高性能,适合复杂应用
    • Cortex-R系列:高性能、实时计算
      • Cortex-R4/R5:用于汽车、工业等安全关键应用
    • Cortex-A系列:应用处理器
      • Cortex-A7/A53:低功耗,适合移动设备
      • Cortex-A72/A73:高性能,适合高端应用
  • RISC-V架构

    • 开源指令集架构
    • 可定制性强
    • 逐渐在嵌入式领域流行
  • 其他架构

    • MIPS:网络设备、路由器
    • PowerPC:工业控制、汽车电子
    • x86:高性能嵌入式系统

42.2.3 处理器选择考虑因素

  • 性能需求:处理能力、时钟频率、指令集
  • 功耗限制:电池寿命要求
  • 内存需求:程序和数据空间
  • 外设接口:所需的I/O接口类型和数量
  • 成本预算:处理器和开发工具成本
  • 生态系统:开发工具、支持库、社区资源
  • 可靠性要求:温度范围、抗干扰能力
  • 生命周期:产品生命周期内的供应保障

42.3 嵌入式系统开发环境

42.3.1 开发工具链

  • 交叉编译器:在主机上编译目标平台的代码

    • GCC交叉编译器:最广泛使用
    • ARM Compiler:ARM官方编译器
    • IAR Embedded Workbench:商业编译器
  • 调试器

    • J-Link:支持ARM Cortex系列
    • ST-Link:STMicroelectronics开发板专用
    • ULINK:Keil开发工具配套
    • GDB:开源调试器
  • 集成开发环境(IDE)

    • Keil MDK:ARM Cortex-M系列开发
    • IAR Embedded Workbench:支持多种处理器
    • STM32CubeIDE:STM32系列专用
    • Eclipse:可通过插件支持嵌入式开发
    • Visual Studio Code:通过扩展支持嵌入式开发
  • 构建工具

    • Make:传统构建工具
    • CMake:跨平台构建工具
    • Ninja:快速构建工具

42.3.2 开发流程

  1. 需求分析:明确系统功能和性能要求
  2. 硬件设计:选择处理器、设计电路
  3. 软件设计:架构设计、模块划分
  4. 代码编写:使用C/C++等语言
  5. 编译链接:生成可执行文件
  6. 烧录调试:将程序烧录到目标设备并调试
  7. 测试验证:功能测试、性能测试、可靠性测试
  8. 部署维护:系统部署和后续维护

42.3.3 开发板选择

  • 评估板:处理器厂商提供的开发板

    • STM32 Nucleo板:STM32系列
    • Arduino板:适合初学者
    • Raspberry Pi:适合Linux嵌入式开发
    • BeagleBone:适合Linux嵌入式开发
  • 定制板:根据具体应用设计的电路板

    • 优点:完全符合应用需求
    • 缺点:开发周期长,成本高
  • 开发套件:包含处理器、外设、调试工具的完整套件

    • 优点:开箱即用,开发便捷
    • 缺点:成本较高

42.4 实时操作系统(RTOS)

42.4.1 实时系统的概念

实时系统是指能够在规定的时间内完成任务的系统,分为:

  • 硬实时系统:必须严格在截止时间前完成任务,否则会导致系统故障

    • 示例:航空航天控制系统、医疗设备、汽车安全系统
  • 软实时系统:尽量在截止时间前完成任务,但偶尔超时不会导致严重后果

    • 示例:视频播放器、游戏控制台、网络设备

42.4.2 常用RTOS

  • FreeRTOS

    • 开源、轻量级
    • 广泛使用,支持多种处理器
    • 提供任务管理、队列、信号量等功能
  • uC/OS-II/III

    • 商业RTOS,可靠性高
    • 用于安全关键应用
    • 通过了多种安全认证
  • ThreadX

    • 实时性强,确定性高
    • 支持多种处理器
    • 小内存 footprint
  • Zephyr

    • Linux基金会支持的开源RTOS
    • 模块化设计,可配置性强
    • 支持多种处理器和通讯协议
  • VxWorks

    • 商业RTOS,用于航空航天等关键领域
    • 功能丰富,可靠性高
    • 价格昂贵

42.4.3 FreeRTOS基础

42.4.3.1 FreeRTOS核心概念

  • 任务:执行特定功能的代码单元
  • 队列:任务间通信的机制
  • 信号量:用于资源同步和互斥
  • 互斥量:用于保护共享资源
  • 事件组:用于事件通知
  • 定时器:软件定时器
  • 任务通知:轻量级的任务间通信机制

42.4.3.2 FreeRTOS任务创建与管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include "FreeRTOS.h"
#include "task.h"

// 任务1函数
void vTask1(void *pvParameters)
{
while(1)
{
// 任务1的代码
printf("Task 1 running\n");
vTaskDelay(pdMS_TO_TICKS(1000)); // 延迟1秒
}
}

// 任务2函数
void vTask2(void *pvParameters)
{
while(1)
{
// 任务2的代码
printf("Task 2 running\n");
vTaskDelay(pdMS_TO_TICKS(500)); // 延迟500毫秒
}
}

int main(void)
{
// 初始化硬件
// ...

// 创建任务
xTaskCreate(vTask1, "Task1", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY + 1, NULL);
xTaskCreate(vTask2, "Task2", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY + 1, NULL);

// 启动调度器
vTaskStartScheduler();

// 调度器启动后不会执行到这里
while(1);
}

42.4.3.3 任务间通信

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"

QueueHandle_t xQueue;

// 发送任务
void vSenderTask(void *pvParameters)
{
uint32_t ulValueToSend = 0;

while(1)
{
// 发送数据到队列
xQueueSend(xQueue, &ulValueToSend, portMAX_DELAY);
ulValueToSend++;
vTaskDelay(pdMS_TO_TICKS(1000));
}
}

// 接收任务
void vReceiverTask(void *pvParameters)
{
uint32_t ulReceivedValue;

while(1)
{
// 从队列接收数据
xQueueReceive(xQueue, &ulReceivedValue, portMAX_DELAY);
printf("Received: %lu\n", ulReceivedValue);
}
}

int main(void)
{
// 创建队列,可存储5个uint32_t类型的数据
xQueue = xQueueCreate(5, sizeof(uint32_t));

if(xQueue != NULL)
{
// 创建任务
xTaskCreate(vSenderTask, "Sender", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY + 1, NULL);
xTaskCreate(vReceiverTask, "Receiver", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY + 2, NULL);

// 启动调度器
vTaskStartScheduler();
}

while(1);
}

42.5 嵌入式C++编程

42.5.1 C++在嵌入式系统中的应用

  • 面向对象设计:更好的代码组织和复用

    • 封装:隐藏实现细节
    • 继承:代码复用
    • 多态:运行时行为选择
  • 模板:编译时多态,提高代码效率

    • 类型安全的容器
    • 通用算法
  • 异常处理:错误处理机制

    • 但在硬实时系统中需谨慎使用
  • STL子集:适用于资源受限环境

    • 如ETL(Embedded Template Library)

42.5.2 嵌入式C++编程注意事项

  • 内存使用

    • 避免动态内存分配,使用静态内存
    • 注意栈大小,避免栈溢出
    • 使用固定大小的容器
  • 代码大小

    • 优化代码,减少ROM使用
    • 禁用未使用的特性
    • 使用链接时优化(LTO)
  • 运行时开销

    • 避免虚函数、异常等开销较大的特性
    • 注意构造函数和析构函数的开销
    • 优化循环和条件分支
  • 实时性

    • 确保代码执行时间可预测
    • 避免不确定的操作
    • 使用实时操作系统
  • 初始化顺序

    • 注意全局对象的初始化顺序
    • 优先使用局部静态变量

42.5.3 嵌入式C++优化技巧

  • 使用constexpr:编译时计算

    1
    2
    3
    4
    5
    constexpr int factorial(int n) {
    return n <= 1 ? 1 : n * factorial(n - 1);
    }

    constexpr int f5 = factorial(5); // 编译时计算
  • 内联函数:减少函数调用开销

    1
    2
    3
    inline int max(int a, int b) {
    return a > b ? a : b;
    }
  • 位操作:高效操作硬件寄存器

    1
    2
    3
    4
    5
    6
    // 设置位
    REGISTER |= (1 << BIT_POS);
    // 清除位
    REGISTER &= ~(1 << BIT_POS);
    // 切换位
    REGISTER ^= (1 << BIT_POS);
  • 固定大小类型:使用std::uint8_t、std::uint32_t等

    1
    2
    3
    4
    5
    #include <cstdint>

    std::uint8_t u8; // 无符号8位整数
    std::int16_t s16; // 有符号16位整数
    std::uint32_t u32; // 无符号32位整数
  • 内存对齐:提高内存访问效率

    1
    2
    3
    4
    5
    6
    7
    #pragma pack(push, 1) // 按1字节对齐
    struct DeviceRegister {
    std::uint8_t status;
    std::uint16_t value;
    std::uint8_t control;
    };
    #pragma pack(pop)
  • 避免使用异常:在硬实时系统中

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // 替代异常的错误处理
    enum class Result {
    Success,
    Error,
    Timeout
    };

    Result readSensor(int& value) {
    // 实现...
    if (error) {
    return Result::Error;
    }
    return Result::Success;
    }

42.6 外设编程

42.6.1 GPIO编程

GPIO(通用输入输出)是最基本的外设,用于控制和读取数字信号。

42.6.1.1 STM32 GPIO示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include "stm32f4xx.h"

// 初始化GPIO
void GPIO_Init(void)
{
// 使能GPIO时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);

GPIO_InitTypeDef GPIO_InitStruct;

// 配置PA5为输出(LED)
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_5;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(GPIOA, &GPIO_InitStruct);

// 配置PA0为输入(按钮)
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN;
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(GPIOA, &GPIO_InitStruct);
}

// 主函数
int main(void)
{
// 初始化系统
SystemInit();

// 初始化GPIO
GPIO_Init();

while(1)
{
// 读取按钮状态
if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0) == RESET)
{
// 按钮按下,点亮LED
GPIO_SetBits(GPIOA, GPIO_Pin_5);
}
else
{
// 按钮释放,熄灭LED
GPIO_ResetBits(GPIOA, GPIO_Pin_5);
}
}
}

42.6.2 串口通信

串口是嵌入式系统中常用的通信接口,用于与其他设备通信。

42.6.2.1 STM32 USART示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
#include "stm32f4xx.h"
#include <stdio.h>

// 初始化USART2
void USART2_Init(void)
{
// 使能GPIO和USART时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);

// 配置GPIO
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(GPIOA, &GPIO_InitStruct);

// 连接GPIO到USART2
GPIO_PinAFConfig(GPIOA, GPIO_PinSource2, GPIO_AF_USART2);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource3, GPIO_AF_USART2);

// 配置USART2
USART_InitTypeDef USART_InitStruct;
USART_InitStruct.USART_BaudRate = 115200;
USART_InitStruct.USART_WordLength = USART_WordLength_8b;
USART_InitStruct.USART_StopBits = USART_StopBits_1;
USART_InitStruct.USART_Parity = USART_Parity_No;
USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStruct.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
USART_Init(USART2, &USART_InitStruct);

// 使能USART2
USART_Cmd(USART2, ENABLE);
}

// 发送字符
void USART2_SendChar(uint8_t ch)
{
while(USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET);
USART_SendData(USART2, ch);
}

// 发送字符串
void USART2_SendString(const char* str)
{
while(*str)
{
USART2_SendChar(*str++);
}
}

// 接收字符
uint8_t USART2_ReceiveChar(void)
{
while(USART_GetFlagStatus(USART2, USART_FLAG_RXNE) == RESET);
return USART_ReceiveData(USART2);
}

// 重定向printf函数
int fputc(int ch, FILE *f)
{
USART2_SendChar(ch);
return ch;
}

int main(void)
{
// 初始化系统
SystemInit();

// 初始化USART2
USART2_Init();

printf("Hello, Embedded Systems!\n");

while(1)
{
// 接收字符并发送回
uint8_t ch = USART2_ReceiveChar();
USART2_SendChar(ch);
}
}

42.6.3 I2C通信

I2C(Inter-Integrated Circuit)是一种串行通信协议,用于短距离设备间通信。

42.6.3.1 STM32 I2C示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
#include "stm32f4xx.h"

// 初始化I2C1
void I2C1_Init(void)
{
// 使能GPIO和I2C时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);

// 配置GPIO
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStruct.GPIO_OType = GPIO_OType_OD;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(GPIOB, &GPIO_InitStruct);

// 连接GPIO到I2C1
GPIO_PinAFConfig(GPIOB, GPIO_PinSource6, GPIO_AF_I2C1);
GPIO_PinAFConfig(GPIOB, GPIO_PinSource7, GPIO_AF_I2C1);

// 配置I2C1
I2C_InitTypeDef I2C_InitStruct;
I2C_InitStruct.I2C_ClockSpeed = 100000; // 100kHz
I2C_InitStruct.I2C_Mode = I2C_Mode_I2C;
I2C_InitStruct.I2C_DutyCycle = I2C_DutyCycle_2;
I2C_InitStruct.I2C_OwnAddress1 = 0x00;
I2C_InitStruct.I2C_Ack = I2C_Ack_Enable;
I2C_InitStruct.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
I2C_Init(I2C1, &I2C_InitStruct);

// 使能I2C1
I2C_Cmd(I2C1, ENABLE);
}

// 向I2C设备写入数据
void I2C1_Write(uint8_t devAddr, uint8_t regAddr, uint8_t data)
{
// 等待I2C总线空闲
while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY));

// 发送起始信号
I2C_GenerateSTART(I2C1, ENABLE);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));

// 发送设备地址(写)
I2C_Send7bitAddress(I2C1, devAddr << 1, I2C_Direction_Transmitter);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));

// 发送寄存器地址
I2C_SendData(I2C1, regAddr);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));

// 发送数据
I2C_SendData(I2C1, data);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));

// 发送停止信号
I2C_GenerateSTOP(I2C1, ENABLE);
}

// 从I2C设备读取数据
uint8_t I2C1_Read(uint8_t devAddr, uint8_t regAddr)
{
uint8_t data;

// 等待I2C总线空闲
while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY));

// 发送起始信号
I2C_GenerateSTART(I2C1, ENABLE);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));

// 发送设备地址(写)
I2C_Send7bitAddress(I2C1, devAddr << 1, I2C_Direction_Transmitter);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));

// 发送寄存器地址
I2C_SendData(I2C1, regAddr);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));

// 重新发送起始信号
I2C_GenerateSTART(I2C1, ENABLE);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));

// 发送设备地址(读)
I2C_Send7bitAddress(I2C1, devAddr << 1, I2C_Direction_Receiver);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED));

// 禁用ACK
I2C_AcknowledgeConfig(I2C1, DISABLE);

// 发送停止信号
I2C_GenerateSTOP(I2C1, ENABLE);

// 读取数据
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED));
data = I2C_ReceiveData(I2C1);

// 重新启用ACK
I2C_AcknowledgeConfig(I2C1, ENABLE);

return data;
}

int main(void)
{
// 初始化系统
SystemInit();

// 初始化I2C1
I2C1_Init();

// 向设备写入数据
I2C1_Write(0x68, 0x00, 0x01);

// 从设备读取数据
uint8_t data = I2C1_Read(0x68, 0x00);

while(1);
}

42.6.4 SPI通信

SPI(Serial Peripheral Interface)是一种高速串行通信协议,用于设备间通信。

42.6.5 ADC与DAC

  • ADC(模数转换器):将模拟信号转换为数字信号
  • DAC(数模转换器):将数字信号转换为模拟信号

42.7 中断与异常处理

42.7.1 中断的概念

中断是处理器暂停当前执行的程序,转而去执行处理特定事件的程序(中断服务程序)的机制。

42.7.2 中断优先级

中断优先级决定了多个中断同时发生时的处理顺序。

42.7.3 中断服务程序

中断服务程序(ISR)是处理中断的函数,应尽量简短,避免使用可能导致阻塞的操作。

42.7.3.1 STM32外部中断示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#include "stm32f4xx.h"

// 初始化外部中断
void EXTI_Init(void)
{
// 使能SYSCFG时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);

// 配置PA0为EXTI0
SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOA, EXTI_PinSource0);

// 配置EXTI0
EXTI_InitTypeDef EXTI_InitStruct;
EXTI_InitStruct.EXTI_Line = EXTI_Line0;
EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Rising;
EXTI_InitStruct.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStruct);

// 配置NVIC
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_InitStruct.NVIC_IRQChannel = EXTI0_IRQn;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruct);
}

// EXTI0中断服务程序
void EXTI0_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line0) != RESET)
{
// 处理中断
// 例如:切换LED状态
GPIO_ToggleBits(GPIOA, GPIO_Pin_5);

// 清除中断标志位
EXTI_ClearITPendingBit(EXTI_Line0);
}
}

int main(void)
{
// 初始化系统
SystemInit();

// 初始化GPIO
GPIO_Init();

// 初始化外部中断
EXTI_Init();

while(1);
}

42.8 内存管理

42.8.1 嵌入式系统内存分区

  • 代码区(ROM/Flash):存储程序代码
  • 只读数据区(ROM/Flash):存储常量
  • 初始化数据区(RAM):存储初始化的全局变量和静态变量
  • 未初始化数据区(RAM):存储未初始化的全局变量和静态变量(BSS段)
  • 堆(RAM):动态内存分配区域
  • 栈(RAM):存储函数调用信息和局部变量

42.8.2 内存分配策略

  • 静态内存分配

    • 在编译时分配内存
    • 速度快,无运行时开销
    • 灵活性差
  • 动态内存分配

    • 在运行时分配内存
    • 灵活性好
    • 有运行时开销,可能产生内存碎片

42.8.3 内存池

内存池是一种预分配内存的技术,用于减少动态内存分配的开销。

42.8.3.1 内存池示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#include <stdint.h>

#define MEMPOOL_SIZE 1024
#define BLOCK_SIZE 32
#define BLOCK_COUNT (MEMPOOL_SIZE / BLOCK_SIZE)

uint8_t mempool[MEMPOOL_SIZE];
uint8_t block_status[BLOCK_COUNT]; // 0: free, 1: used

// 初始化内存池
void mempool_init(void)
{
for(int i = 0; i < BLOCK_COUNT; i++)
{
block_status[i] = 0;
}
}

// 分配内存
void* mempool_alloc(void)
{
for(int i = 0; i < BLOCK_COUNT; i++)
{
if(block_status[i] == 0)
{
block_status[i] = 1;
return &mempool[i * BLOCK_SIZE];
}
}
return NULL; // 内存池已满
}

// 释放内存
void mempool_free(void* ptr)
{
if(ptr >= mempool && ptr < mempool + MEMPOOL_SIZE)
{
int block_index = ((uint8_t*)ptr - mempool) / BLOCK_SIZE;
if(block_index >= 0 && block_index < BLOCK_COUNT)
{
block_status[block_index] = 0;
}
}
}

// 测试内存池
void test_mempool(void)
{
mempool_init();

// 分配内存
void* p1 = mempool_alloc();
void* p2 = mempool_alloc();

// 使用内存
if(p1) {
*((uint32_t*)p1) = 0x12345678;
}
if(p2) {
*((uint32_t*)p2) = 0x87654321;
}

// 释放内存
mempool_free(p1);
mempool_free(p2);
}

42.8.4 内存优化技巧

  • 使用合适的变量类型

    • 优先使用无符号类型
    • 选择最小的合适类型
  • 减少全局变量

    • 使用局部变量
    • 使用静态局部变量存储需要保持状态的数据
  • 优化栈使用

    • 避免深层递归
    • 避免在栈上分配大数组
  • 使用放置new

    1
    2
    3
    4
    5
    6
    7
    // 在预分配的内存上构造对象
    void* buffer = mempool_alloc();
    MyClass* obj = new (buffer) MyClass();

    // 显式调用析构函数
    obj->~MyClass();
    mempool_free(buffer);

42.9 电源管理

42.9.1 嵌入式系统的电源模式

  • 运行模式:正常运行,功耗最高
  • 睡眠模式:CPU停止,外设继续工作
  • 停机模式:CPU和大部分外设停止
  • 待机模式:只有基本电路工作,功耗最低

42.9.2 STM32低功耗模式示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include "stm32f4xx.h"

// 进入睡眠模式
void enter_sleep_mode(void)
{
// 配置睡眠模式
SCB->SCR &= ~SCB_SCR_SLEEPDEEP_Msk;

// 等待中断
__WFI();
}

// 进入停机模式
void enter_stop_mode(void)
{
// 配置停机模式
SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;
PWR->CR |= PWR_CR_PDDS;
PWR->CR &= ~PWR_CR_CSBF;

// 等待中断
__WFI();
}

// 进入待机模式
void enter_standby_mode(void)
{
// 配置待机模式
SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;
PWR->CR |= PWR_CR_PDDS;
PWR->CR |= PWR_CR_CSBF;

// 等待中断
__WFI();
}

int main(void)
{
// 初始化系统
SystemInit();

while(1)
{
// 执行任务
// ...

// 进入低功耗模式
enter_sleep_mode();
// 或 enter_stop_mode();
// 或 enter_standby_mode();
}
}

42.9.3 低功耗设计技巧

  • 硬件设计

    • 选择低功耗处理器
    • 使用低功耗外设
    • 优化电源电路
  • 软件设计

    • 减少处理器活动时间
    • 使用中断而非轮询
    • 合理使用低功耗模式
    • 关闭未使用的外设
  • 系统设计

    • 优化任务调度
    • 减少通信频率
    • 使用能量 harvesting(能量收集)技术

42.10 嵌入式系统调试

42.10.1 调试工具

  • 仿真器

    • J-Link
    • ST-Link
    • ULINK
  • 示波器

    • 观察模拟信号
    • 测量信号时序
  • 逻辑分析仪

    • 分析数字信号
    • 捕获总线数据
  • 串口调试

    • 通过串口输出调试信息
    • 使用printf重定向
  • 在线调试器(ICD)

    • 实时调试程序
    • 查看变量和寄存器

42.10.2 调试技术

  • 断点调试

    • 在代码中设置断点
    • 暂停程序执行
    • 查看变量状态
  • 单步执行

    • 逐行执行代码
    • 观察代码执行流程
  • 变量监控

    • 实时查看变量值
    • 设置变量观察点
  • 内存查看

    • 检查内存内容
    • 验证内存操作
  • 性能分析

    • 测量代码执行时间
    • 识别性能瓶颈

42.10.3 常见问题排查

  • 硬件问题

    • 电路连接错误
    • 电源供应不稳定
    • 信号完整性问题
  • 软件问题

    • 逻辑错误
    • 内存泄漏
    • 栈溢出
    • 中断处理错误
  • 实时性问题

    • 中断延迟
    • 任务调度问题
    • 死锁
  • 功耗问题

    • 未使用低功耗模式
    • 外设未正确关闭

42.11 项目实战:温湿度监测系统

42.11.1 项目需求

  • 功能需求

    • 实时监测环境温度和湿度
    • 通过串口发送数据到上位机
    • 当温湿度超过阈值时触发报警
    • 支持低功耗模式
  • 性能需求

    • 测量精度:温度±0.5℃,湿度±5%
    • 采样频率:1次/秒
    • 响应时间:<1秒
  • 硬件需求

    • 处理器:STM32L432KC(低功耗MCU)
    • 传感器:DHT11(温湿度传感器)
    • 通信:USART2
    • 电源:3.3V

42.11.2 硬件设计

  • STM32L432KC开发板

    • 低功耗Cortex-M4处理器
    • 内置温度传感器
    • 丰富的外设
  • DHT11传感器

    • 单总线通信
    • 测量范围:温度0-50℃,湿度20-90%
    • 供电电压:3.3V-5V
  • 电路连接

    • DHT11 DATA引脚 → PA0
    • DHT11 VCC引脚 → 3.3V
    • DHT11 GND引脚 → GND

42.11.3 软件设计

42.11.3.1 系统架构

  • 主任务:协调各模块工作
  • 传感器任务:读取温湿度数据
  • 通信任务:发送数据到上位机
  • 报警任务:处理报警逻辑
  • 低功耗管理:控制电源模式

42.11.3.2 代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
#include "stm32l4xx.h"
#include <stdio.h>

// 全局变量
uint8_t temperature = 0;
uint8_t humidity = 0;
bool alarm = false;

// 函数声明
void System_Init(void);
void GPIO_Init(void);
void USART2_Init(void);
void DHT11_ReadData(uint8_t *temp, uint8_t *hum);
void USART2_SendString(const char* str);
void USART2_SendNumber(uint32_t num);
void enter_low_power_mode(void);

// 主函数
int main(void)
{
// 系统初始化
System_Init();

while(1)
{
// 读取温湿度数据
DHT11_ReadData(&temperature, &humidity);

// 发送数据到上位机
USART2_SendString("Temperature: ");
USART2_SendNumber(temperature);
USART2_SendString(" C, Humidity: ");
USART2_SendNumber(humidity);
USART2_SendString("%\r\n");

// 检查阈值
if(temperature > 30 || humidity > 80)
{
alarm = true;
USART2_SendString("Alarm: Temperature or humidity too high!\r\n");
}
else
{
alarm = false;
}

// 进入低功耗模式
enter_low_power_mode();
}
}

// 系统初始化
void System_Init(void)
{
// 初始化系统时钟
SystemCoreClockUpdate();

// 初始化GPIO
GPIO_Init();

// 初始化USART2
USART2_Init();
}

// GPIO初始化
void GPIO_Init(void)
{
// 使能GPIO时钟
RCC_AHB2PeriphClockCmd(RCC_AHB2Periph_GPIOA, ENABLE);

GPIO_InitTypeDef GPIO_InitStruct;

// 配置PA0为输出(DHT11)
GPIO_InitStruct.Pin = GPIO_Pin_0;
GPIO_InitStruct.Mode = GPIO_Mode_OUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_Speed_LOW;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

// 配置PA2和PA3为复用功能(USART2)
GPIO_InitStruct.Pin = GPIO_Pin_2 | GPIO_Pin_3;
GPIO_InitStruct.Mode = GPIO_Mode_AF;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_Speed_LOW;
GPIO_InitStruct.Alternate = GPIO_AF7_USART2;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}

// USART2初始化
void USART2_Init(void)
{
// 使能USART2时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);

USART_InitTypeDef USART_InitStruct;
USART_InitStruct.BaudRate = 9600;
USART_InitStruct.WordLength = USART_WordLength_8b;
USART_InitStruct.StopBits = USART_StopBits_1;
USART_InitStruct.Parity = USART_Parity_No;
USART_InitStruct.Mode = USART_Mode_Tx;
USART_InitStruct.HardwareFlowControl = USART_HardwareFlowControl_None;
USART_Init(USART2, &USART_InitStruct);

// 使能USART2
USART_Cmd(USART2, ENABLE);
}

// 发送字符串
void USART2_SendString(const char* str)
{
while(*str)
{
while(USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET);
USART_SendData(USART2, *str++);
}
}

// 发送数字
void USART2_SendNumber(uint32_t num)
{
char buffer[10];
uint8_t i = 0;

if(num == 0)
{
USART2_SendString("0");
return;
}

while(num > 0)
{
buffer[i++] = num % 10 + '0';
num /= 10;
}

while(i > 0)
{
USART2_SendString(&buffer[--i]);
}
}

// 读取DHT11数据
void DHT11_ReadData(uint8_t *temp, uint8_t *hum)
{
uint8_t data[5] = {0};
uint8_t i, j;

// 发送开始信号
HAL_GPIO_WritePin(GPIOA, GPIO_Pin_0, GPIO_PIN_RESET);
HAL_Delay(20); // 延迟至少18ms
HAL_GPIO_WritePin(GPIOA, GPIO_Pin_0, GPIO_PIN_SET);
HAL_Delay(20); // 延迟20-40us

// 配置为输入
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.Pin = GPIO_Pin_0;
GPIO_InitStruct.Mode = GPIO_Mode_IN;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

// 等待DHT11响应
while(HAL_GPIO_ReadPin(GPIOA, GPIO_Pin_0) == GPIO_PIN_SET);
while(HAL_GPIO_ReadPin(GPIOA, GPIO_Pin_0) == GPIO_PIN_RESET);
while(HAL_GPIO_ReadPin(GPIOA, GPIO_Pin_0) == GPIO_PIN_SET);

// 读取40位数据
for(i = 0; i < 5; i++)
{
for(j = 0; j < 8; j++)
{
while(HAL_GPIO_ReadPin(GPIOA, GPIO_Pin_0) == GPIO_PIN_RESET);

// 延迟判断高电平持续时间
HAL_Delay(1);

data[i] <<= 1;
if(HAL_GPIO_ReadPin(GPIOA, GPIO_Pin_0) == GPIO_PIN_SET)
{
data[i] |= 1;
while(HAL_GPIO_ReadPin(GPIOA, GPIO_Pin_0) == GPIO_PIN_SET);
}
}
}

// 配置为输出
GPIO_InitStruct.Pin = GPIO_Pin_0;
GPIO_InitStruct.Mode = GPIO_Mode_OUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_Speed_LOW;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
HAL_GPIO_WritePin(GPIOA, GPIO_Pin_0, GPIO_PIN_SET);

// 检查校验和
if(data[0] + data[1] + data[2] + data[3] == data[4])
{
*hum = data[0];
*temp = data[2];
}
}

// 进入低功耗模式
void enter_low_power_mode(void)
{
// 延迟1秒
HAL_Delay(1000);

// 进入睡眠模式
__WFI();
}

42.11.4 测试与验证

  • 功能测试

    • 测量不同环境下的温湿度
    • 验证报警功能
    • 测试低功耗模式
  • 性能测试

    • 测量采样频率
    • 验证响应时间
    • 测试电池寿命
  • 可靠性测试

    • 长时间运行测试
    • 温度湿度循环测试
    • 电源波动测试

42.11.5 项目总结

本项目实现了一个基于STM32L432KC的低功耗温湿度监测系统,具有以下特点:

  • 功能完整:实现了温湿度监测、数据传输、报警等功能
  • 低功耗设计:使用STM32L432KC低功耗MCU,支持睡眠模式
  • 成本低廉:使用DHT11传感器,成本低
  • 易于扩展:可添加显示屏、无线通信等功能

通过本项目的实践,我们学习了嵌入式系统的硬件设计、软件编程、低功耗优化等技术,为更复杂的嵌入式系统开发打下了基础。

42.12 小结

本章介绍了嵌入式系统编程的相关知识,包括:

  • 嵌入式系统概述:定义、特点、应用领域
  • 嵌入式处理器:类型、架构、选择考虑因素
  • 开发环境:工具链、开发流程、开发板
  • 实时操作系统:FreeRTOS基础、任务管理、通信机制
  • 嵌入式C++编程:应用、注意事项、优化技巧
  • 外设编程:GPIO、串口、I2C、SPI等
  • 中断与异常处理:概念、优先级、ISR设计
  • 内存管理:内存分区、分配策略、优化技巧
  • 电源管理:低功耗模式、优化技巧
  • 调试技术:工具、技术、问题排查
  • 项目实战:温湿度监测系统的设计与实现

嵌入式系统编程是一项综合性的技术,需要掌握硬件、软件、系统等多个领域的知识。通过不断学习和实践,我们可以开发出更加高效、可靠、低功耗的嵌入式系统,为各种智能设备和物联网应用提供核心技术支持。

作为现代C++程序员,我们应该充分利用C++的特性,如面向对象编程、模板、RAII等,结合嵌入式系统的特点,编写高质量的嵌入式代码。同时,我们也应该关注最新的嵌入式技术发展,如RISC-V架构、边缘计算、AI在嵌入式系统中的应用等,不断提升自己的技术水平。