第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 开发流程
- 需求分析:明确系统功能和性能要求
- 硬件设计:选择处理器、设计电路
- 软件设计:架构设计、模块划分
- 代码编写:使用C/C++等语言
- 编译链接:生成可执行文件
- 烧录调试:将程序烧录到目标设备并调试
- 测试验证:功能测试、性能测试、可靠性测试
- 部署维护:系统部署和后续维护
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"
void vTask1(void *pvParameters) { while(1) { printf("Task 1 running\n"); vTaskDelay(pdMS_TO_TICKS(1000)); } }
void vTask2(void *pvParameters) { while(1) { printf("Task 2 running\n"); vTaskDelay(pdMS_TO_TICKS(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) { 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++优化技巧
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"
void GPIO_Init(void) { RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); GPIO_InitTypeDef GPIO_InitStruct; 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); 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_Init(); while(1) { if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0) == RESET) { GPIO_SetBits(GPIOA, GPIO_Pin_5); } else { 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>
void USART2_Init(void) { RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE); 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_PinAFConfig(GPIOA, GPIO_PinSource2, GPIO_AF_USART2); GPIO_PinAFConfig(GPIOA, GPIO_PinSource3, GPIO_AF_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); 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); }
int fputc(int ch, FILE *f) { USART2_SendChar(ch); return ch; }
int main(void) { SystemInit(); 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"
void I2C1_Init(void) { RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE); 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_PinAFConfig(GPIOB, GPIO_PinSource6, GPIO_AF_I2C1); GPIO_PinAFConfig(GPIOB, GPIO_PinSource7, GPIO_AF_I2C1); I2C_InitTypeDef I2C_InitStruct; I2C_InitStruct.I2C_ClockSpeed = 100000; 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); I2C_Cmd(I2C1, ENABLE); }
void I2C1_Write(uint8_t devAddr, uint8_t regAddr, uint8_t data) { 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); }
uint8_t I2C1_Read(uint8_t devAddr, uint8_t regAddr) { uint8_t data; 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)); I2C_AcknowledgeConfig(I2C1, DISABLE); I2C_GenerateSTOP(I2C1, ENABLE); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED)); data = I2C_ReceiveData(I2C1); I2C_AcknowledgeConfig(I2C1, ENABLE); return data; }
int main(void) { SystemInit(); 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) { RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE); SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOA, EXTI_PinSource0); 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_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); }
void EXTI0_IRQHandler(void) { if(EXTI_GetITStatus(EXTI_Line0) != RESET) { GPIO_ToggleBits(GPIOA, GPIO_Pin_5); EXTI_ClearITPendingBit(EXTI_Line0); } }
int main(void) { SystemInit(); 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];
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(); } }
|
42.9.3 低功耗设计技巧
硬件设计:
软件设计:
- 减少处理器活动时间
- 使用中断而非轮询
- 合理使用低功耗模式
- 关闭未使用的外设
系统设计:
- 优化任务调度
- 减少通信频率
- 使用能量 harvesting(能量收集)技术
42.10 嵌入式系统调试
42.10.1 调试工具
仿真器:
示波器:
逻辑分析仪:
串口调试:
在线调试器(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_Init(); USART2_Init(); }
void GPIO_Init(void) { RCC_AHB2PeriphClockCmd(RCC_AHB2Periph_GPIOA, ENABLE); GPIO_InitTypeDef GPIO_InitStruct; 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); 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); }
void USART2_Init(void) { 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); 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]); } }
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); HAL_GPIO_WritePin(GPIOA, GPIO_Pin_0, GPIO_PIN_SET); HAL_Delay(20); 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); 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); 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) { 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在嵌入式系统中的应用等,不断提升自己的技术水平。