第2章 嵌入式系统基础回顾:微控制器与微处理器区别、实时操作系统(RTOS)概念、GPIO/ADC/PWM等外设基础
各位同学,咱们正式开始之前,我先问个问题:你手里拿的开发板,到底是微控制器还是微处理器?
这个问题我当年刚入行时也搞混过。记得有一次项目选型,我拿着STM32F4的板子跟客户说这是“处理器”,结果被老工程师当场纠正。嗯,从那以后我才认真去琢磨这两者的区别。
2.1 微控制器 vs 微处理器:到底差在哪?
说白了,微控制器(MCU)就是“麻雀虽小五脏俱全”的典型。它把CPU、内存、Flash、各种外设都塞进一颗芯片里。你给它通上电,接个晶振,就能跑起来。
微处理器(MPU)呢?它更像一个“光杆司令”。只有CPU核心,你得自己给它配内存、配存储、配外设接口。典型的比如ARM Cortex-A系列、x86系列。
我个人的习惯是这么区分的:
- MCU:跑裸机或RTOS,做控制类任务,比如智能家居、电机控制、传感器采集
- MPU:跑Linux或Windows,做复杂计算,比如路由器、工业平板、边缘网关
| 对比项 | 微控制器(MCU) | 微处理器(MPU) |
|---|---|---|
| 集成度 | 高(CPU+RAM+Flash+外设) | 低(仅CPU核心) |
| 功耗 | 低(毫瓦级) | 高(瓦级) |
| 实时性 | 强(可硬实时) | 弱(依赖OS调度) |
| 典型应用 | 传感器节点、电机控制 | 路由器、工业计算机 |
| 成本 | 低(几元到几十元) | 高(几十到几百元) |
重点记住:做嵌入式安全检测时,MCU设备往往资源受限,你没法跑复杂的加密算法。而MPU设备虽然性能强,但攻击面也更大——Linux系统漏洞、网络协议栈漏洞,都是我们需要关注的点。
2.2 实时操作系统(RTOS):为什么需要它?
你想想看,如果让你用while循环去轮询三个按键、一个串口、一个ADC采样,还要保证每个任务都不丢数据……是不是头大?
RTOS就是来解决这个问题的。它帮你把任务拆开,让每个任务都有自己的“时间片”。
我最早接触RTOS是在一个四轴飞行器项目上。当时用裸机写姿态解算,总是出现传感器数据丢失。后来换成FreeRTOS,把传感器读取、姿态解算、PWM输出分成三个独立任务,问题一下子就解决了。
2.2.1 RTOS的核心概念
- 任务(Task):每个任务就是一个无限循环的函数,有自己的栈空间
- 调度器(Scheduler):决定哪个任务该运行,常见的有抢占式调度和协作式调度
- 信号量(Semaphore):用来同步任务,比如“数据采集完了,通知处理任务开始工作”
- 消息队列(Queue):任务之间传递数据,比全局变量安全得多
我的经验:刚开始用RTOS时,最容易犯的错误就是任务栈分配太小。系统跑着跑着就莫名其妙死机了。我建议你至少给每个任务分配512字节以上的栈空间,调试时打开栈溢出检测功能。
2.2.2 一个简单的RTOS任务示例
// FreeRTOS 示例:创建两个任务
void vTask1(void *pvParameters) {
while(1) {
// 读取传感器数据
sensor_data = read_sensor();
// 发送到队列
xQueueSend(xQueue, &sensor_data, portMAX_DELAY);
vTaskDelay(pdMS_TO_TICKS(100)); // 100ms执行一次
}
}
void vTask2(void *pvParameters) {
while(1) {
// 从队列接收数据
if(xQueueReceive(xQueue, &data, portMAX_DELAY)) {
// 处理数据并判断异常
if(data > THRESHOLD) {
trigger_alarm();
}
}
}
}
// 在main中创建任务
xTaskCreate(vTask1, "Sensor", 256, NULL, 1, NULL);
xTaskCreate(vTask2, "Process", 256, NULL, 2, NULL);
vTaskStartScheduler();
注意:在RTOS中,千万不要用delay()这种阻塞函数。要用vTaskDelay()让出CPU。我曾经见过有人用HAL_Delay()导致整个系统卡死——因为调度器没法运行了。
2.3 GPIO/ADC/PWM:嵌入式开发的“三驾马车”
这三个外设,你几乎在每个嵌入式项目里都会碰到。咱们一个一个说。
2.3.1 GPIO:最基础也最容易出错
GPIO就是通用输入输出引脚。你可以把它配置成输入(读按键、读传感器电平)或输出(控制LED、继电器)。
我个人习惯在初始化时做三件事:
- 使能GPIO时钟(很多人忘了这一步)
- 配置模式(输入/输出/复用功能)
- 设置上下拉电阻(防止引脚浮空)
// STM32 GPIO 初始化示例
void GPIO_Init(void) {
__HAL_RCC_GPIOA_CLK_ENABLE(); // 使能时钟,这一步不能忘
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = GPIO_PIN_0 | GPIO_PIN_1;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; // 推挽输出
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}
避坑指南:我曾经在一个项目中,GPIO配置成开漏输出却忘了加上拉电阻,结果信号一直拉不高。排查了两天才发现是硬件问题。所以,开漏输出一定要配合外部上拉电阻使用。
2.3.2 ADC:把模拟世界数字化
ADC(模数转换器)用来测量电压。比如温度传感器输出0-3.3V的模拟电压,ADC把它转成0-4095的数字值(如果是12位ADC)。
实际项目中,ADC采样有几个坑要注意:
- 采样时间:输入阻抗大的信号源需要更长的采样时间,否则测量不准
- 参考电压:Vref+必须稳定,否则测量值会漂移
- 滤波:单次采样不可靠,建议做多次采样取平均
// ADC 多次采样取平均
uint32_t ADC_Read_Average(void) {
uint32_t sum = 0;
for(int i = 0; i < 10; i++) {
HAL_ADC_Start(&hadc1);
HAL_ADC_PollForConversion(&hadc1, 100);
sum += HAL_ADC_GetValue(&hadc1);
}
return sum / 10; // 返回平均值
}
2.3.3 PWM:精确控制的力量
PWM(脉冲宽度调制)说白了就是快速开关信号,通过调节占空比来控制平均电压。最常见的应用就是调LED亮度、控制电机转速。
PWM有三个关键参数:
- 频率:开关的快慢。控制电机一般用1kHz-20kHz,调光用100Hz以上(避免闪烁)
- 占空比:高电平时间占整个周期的比例,0%-100%
- 分辨率:比如8位PWM有256级占空比可调
我的经验:做电机控制时,PWM频率不能太低,否则电机会发出刺耳的啸叫声。我一般选10kHz以上,人耳就听不到了。另外,启动时占空比要从0慢慢增加,直接给100%的话,电机可能会“跳”起来。
2.4 小结:这些基础跟异常检测有什么关系?
你可能会问:讲这些基础干嘛?我们不是要做异常检测吗?
关系大了去了。你想想看:
- GPIO电平异常抖动,可能是外部干扰或引脚虚焊
- ADC采样值突然跳变,可能是传感器老化或电源不稳
- PWM输出波形畸变,可能是定时器配置错误或负载短路
- RTOS任务卡死,可能是死锁或栈溢出
这些,都是我们后面要检测的“异常”。
所以,先把基础打牢。下一章,咱们就开始搭建第一个异常检测实验环境。