嵌入式相关

嵌入式相关知识点

嵌入式系统:以应用为中心、软硬件可裁剪的专用计算机系统,嵌入到设备中执行特定任务(如智能手环、工业控制器)

特点

  • 实时性:工业控制需毫秒级响应(如机器人紧急制动)
  • 低功耗:电池设备需优化能耗(如物联网传感器)
  • 资源受限:内存/处理器性能远低于PC(ESP8266仅80KB RAM)

基础知识

电路基础

基尔霍夫定律

  • 基尔霍夫电流定律(KCL):在集总参数电路中,任意时刻,对任意节点流出或流入该节点电流的代数和等于零

    image-20250919085902707 $$ i_1+i_2+i_3=i_4+i_5 $$
  • 基尔霍夫电压定律(KVL):在集总参数电路中,任一时刻,沿任一闭合路径绕行,各支路电压的代数和等于零

    image-20250919085928634 $$ U_{S1}-U_{S2}-U_2-U_1=0 $$

模拟数字电路

  • 数字电路是处理数字信号的电路。
    数字信号:数字信号是离散的电信号。只有0和1区分。
  • 模拟电路是处理模拟信号的电路。
    模拟信号:模拟信号是连续的电信号。以电压为纵坐标,时间t为横坐标为例,函数曲线是连续的,不存在突然阶跃的情况

单片机基础

计算机的最小组成

计算机的组成非常复杂,但其基本单元非常简单

  1. 笔记本电路板上有很多芯片,一个芯片就是一个系统
  2. 一个系统由很多模块组成,如加法器,乘法器等
  3. 一个模块由很多逻辑门组成,如非门,与门,或门等
  4. 逻辑门由晶体管组成,如PMOS管和NMOS管灯

芯片 => 很多模块组成 => 很多逻辑门组成 => 很多晶体管组成

现代智能手机芯片中的晶体管数量已经达到数百亿级别

image-20250919090701683

硬件组成三要素

组件 作用 实例
传感器 感知物理量(温度/光照/位置)并转为电信号 光电传感器、编码器
控制器 处理传感器数据并决策(核心为MCU或SoC) STM32、ESP32
执行器 执行控制指令(机械运动/开关控制) 直流电机、气缸

控制器

SoC

系统级芯片

System on Chip: SoC 是将整个计算机系统的各个组件(如 CPU、GPU、内存、I/O 接口、通信模块等)集成到单个芯片上的解决方案

典型实例:

  • Raspberry Pi:使用 Broadcom SoC 的单板计算机,适合各种计算和嵌入式应用。
  • 手机芯片(如 Qualcomm Snapdragon、Apple A 系列):集成了 CPU、GPU、通信模块(如 4G/5G)、图像处理器等。
  • NVIDIA Jetson 系列:用于人工智能和机器学习的 SoC,集成了强大的 GPU 和处理能力。
  • ESP32:虽然它也可以被视为 MCU,但它集成了 Wi-Fi 和蓝牙功能,因此在某些上下文中也可以被视为 SoC。

特点

  • 功能强大,集成度高
  • 支持多种处理任务和外设
  • 通常具有较高的功耗和性能
  • 适合需要高性能和多功能的应用

MCU

微控制器单元

定义:MCU 是一种集成电路,通常包括一个或多个处理核心、内存(RAM 和 ROM)、输入输出端口(I/O)和其他外设(如定时器、ADC 等)

用途:MCU 通常用于控制应用,如家电、汽车、工业控制等。它们适合处理简单的控制任务和实时处理

特点

  • 功耗低
  • 处理能力较弱,适合简单任务
  • 通常具有较小的存储空间
  • 设计简单,适合嵌入式系统

典型实例

  1. Arduino:基于 AVR 或 ARM 微控制器的开发板,广泛用于教育和原型设计。
  2. STM32 系列:STMicroelectronics 提供的 32 位微控制器,广泛应用于嵌入式系统。
  3. PIC 微控制器:Microchip Technology 提供的系列微控制器,常用于工业控制和消费电子产品。
  4. 8051 微控制器:一种经典的 8 位微控制器架构,应用于许多嵌入式系统。

软件核心层级

  • 裸机开发:直接操作寄存器控制硬件(C语言点灯实验)
  • RTOS(实时操作系统)
    • 必要性: 多任务调度(如同时处理Wi-Fi通信 + 电机控制)
    • 常用系统: FreeRTOS(ESP8266兼容),Zephyr
  • 通信协议
    • 设备间: I²C、SPI、UART(传感器与控制器通信)
    • 网络层: MQTT(物联网云通信)、Modbus(工业控制)

FreeRTOS架构解析

FreeRTOS 通过精妙的分层架构确定性调度策略硬件级优化实现实时低延迟与优先级抢占,其框架设计可通过以下结构图及核心机制解析

FreeRTOS通过 分层微内核+硬件加速调度+中断优先级隔离 实现硬实时

  • 抢占式调度确保高优先级任务即刻响应

  • 优先级继承消除资源竞争延迟

  • FromISR API 缩短中断到任务唤醒路径

  • Tickless模式兼顾低功耗与定时精度

    开发者需合理配置 FreeRTOSConfig.h(如调度策略、堆大小)并优化任务优先级分配,以发挥其极致实时性能

设计哲学微内核架构仅包含调度、任务同步等必要功能,内核代码精简至几KB,通过 FreeRTOSConfig.h动态裁剪模块(如关闭定时器以节省资源)

1
2
3
4
5
graph TD
A[应用层] -->|调用API| B(FreeRTOS API层)
B -->|依赖内核服务| C[内核层]
C -->|硬件抽象接口| D[移植层]
D -->|寄存器操作| E[硬件层]

任务调度机制(状态转换图)

1
2
3
4
5
6
7
8
stateDiagram-v2
[*] --> 创建态 : xTaskCreate()
创建态 --> 就绪态 : vTaskStartScheduler()
就绪态 --> 运行态 : 调度器选择(优先级抢占)
运行态 --> 阻塞态 : vTaskDelay()/队列等待
阻塞态 --> 就绪态 : 事件触发(如信号量释放)
运行态 --> 挂起态 : vTaskSuspend()
挂起态 --> 就绪态 : vTaskResume()

核心调度策略

  1. 优先级抢占

    • 高优先级任务就绪时立即抢占低优先级任务
    • 优先级位图算法(uxTopReadyPriority)在常数时间内定位最高优先级任务
  2. 时间片轮转

    同优先级任务共享CPU时间(通过SysTick中断触发切换)

  3. 调度确定性保障

    任务切换时间<=1μs(Cortex-M),依赖硬件加速的上下文保存(PendSV异常自动压栈R0-R3等)

低延迟实现机制

中断管理框架
1
2
3
4
5
6
flowchart TB
硬件中断 -->|触发| ISR[中断服务例程]
ISR -->|快速处理| A{需调用FreeRTOS API?}
A -->|是| B[使用FromISR API<br>(如xSemaphoreGiveFromISR)]
A -->|否| C[直接返回]
B -->|触发| D[即时任务切换<br>(portYIELD_FROM_ISR)]

中断优先级分层

  • 不可屏蔽中断(优先级≤configMAX_SYSCALL_INTERRUPT_PRIORITY):禁止调用API,响应延迟极低(如电机控制)
  • 可屏蔽中断(优先级>阈值):可安全调用API,但可能被内核短暂阻塞
优先级继承解决资源竞争
1
2
3
4
5
6
7
8
9
sequenceDiagram
participant 高优先级任务
participant 互斥量
participant 低优先级任务

高优先级任务->>+互斥量: 请求获取(阻塞)
互斥量->>+低优先级任务: 临时提升其优先级
低优先级任务->>-互斥量: 释放资源
互斥量->>-高优先级任务: 恢复运行

互斥锁(Mutex)动态提升低优先级任务的优先级,避免优先级反转导致高优先级任务阻塞

低功耗与实时性平衡(Tickless模式)

空闲时关闭SysTick中断,由高精度RTC唤醒并补偿节拍计数,减少功耗且不牺牲调度精度

开发工具链

工具类型 代表软件 用途
IDE PlatformIO(vscode插件)、Keil 代码编辑/编译/调试
调试器 J-Link、ST-Link 硬件程序烧录与实时调试
仿真器 Proteus、QEMU 无硬件时的逻辑验证

自动化设备开发路径

阶段1:基础实践(2-4周)

  1. 硬件入门

    • 购买ESP32开发板(兼容Wi-Fi/蓝牙)。
    • 完成实验:GPIO控制LED、ADC读取温湿度传感器。
  2. 软件框架

    • 用PlatformIO搭建开发环境(支持VS Code)。
    • 编写C程序驱动外设(如按键触发电机)。

阶段2:系统进阶(4-8周)

  1. RTOS实战

    • 在ESP32上移植FreeRTOS,创建多任务(任务1:传感器采集;任务2:网络上报)。
  2. 通信协议

    • 通过ESP32的Wi-Fi发送传感器数据至云平台(MQTT协议)。

阶段3:自动化设备集成(8+周)

  • 项目案例:智能浇水系统

伪代码示例:土壤湿度控制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void task_sensor(void *pvParams) {
while(1) {
int moisture = read_sensor(); // 读取湿度传感器
xQueueSend(data_queue, &moisture, 0); // 发送到队列
vTaskDelay(5000); // 5秒采集一次
}
}

void task_control(void *pvParams) {
while(1) {
int moisture;
xQueueReceive(data_queue, &moisture, portMAX_DELAY);
if (moisture < 30) digitalWrite(PUMP_PIN, HIGH); // 启动水泵
}
}

硬件配置:

  • 传感器:土壤湿度传感器 → ESP32 → 继电器控制水泵。

资源推荐

  • 书籍:《嵌入式系统设计与实践》(O’Reilly)
  • 课程:Coursera《Embedded Hardware and Operating Systems》
  • 社区:GitHub(开源硬件项目)、EEVblog(硬件调试技巧)

关键提示:

从软件工程师到硬件融合的转型核心在于:

  1. 动手为先:拆解旧家电(如遥控车)理解电路设计。
  2. 渐进式学习:从Arduino快速原型到专业RTOS开发。
  3. 利用现有优势:C/C++能力可快速切入驱动开发,网络知识助力物联网应用。

通过此路径,您可在3-6个月内构建完整的自动化设备开发能力,将软件经验转化为硬件创新力!

ESP开发板对比

特性 ESP8266 ESP32(基础款) ESP32-C3(推荐替代)
处理器 单核 80MHz 双核 240MHz 单核 RISC-V 160MHz
内存 80KB RAM 520KB RAM 400KB RAM
无线功能 Wi-Fi 4 (802.11b/g/n) Wi-Fi 4 + 蓝牙4.2/BLE Wi-Fi 4 + 蓝牙5.0
ADC精度/数量 1路(10位,0-1V量程) 18路(12位,0-3.3V) 6路(12位)
PWM可用通道 8路 16路 4路
典型成本 10-15元 20-30元 15-20元
  • ESP8266:仅用于“触发型”场景(如收数据→发指令),复杂逻辑(如电机PID控制)必选ESP32。
  • ESP32全系:新项目无脑入!ESP32-C3是ESP8266的理想升级,ESP32-S3则满足AI等前沿需求
  • 避坑点:ESP8266的ADC仅1路且量程窄(0-1V),直接接传感器易烧毁,务必分压电路

GPIO

GPIO(General-Purpose Input/Output,通用输入输出接口)是嵌入式系统中最基础的硬件交互通道,可直接连接传感器、执行器或其他电子模块,实现芯片与物理世界的双向通信

硬件结构上看: 每个 GPIO 对应芯片的物理引脚(如 ESP8266 的 D0~D8),内部通过寄存器控制状态(无需外接复杂电路即可驱动 LED 或读取按钮)

GPIO 是嵌入式开发的“手脚” —— 通过它:

  1. 感知世界(输入)→ 接收按钮、传感器信号。

  2. 操控设备(输出)→ 驱动电机、点亮灯光。

    行动建议

    • 入门套件推荐:购买 ESP32 开发板 + GPIO 实验套件(含按钮/LED/光敏电阻),花费约¥50。

    • 快速验证思路:用 Tinkercad 等电路仿真工具设计逻辑,再动手焊接。

      掌握 GPIO 后,您即可将 C++ 算法能力转化为实体自动化设备的控制力!

仿真软件

层级 语言/工具 作用 用户操作示例
用户编写代码 Arduino C/C++ 控制虚拟硬件(如点亮LED、读取传感器) void loop() { digitalWrite(13, HIGH); delay(1000); }
仿真环境内核 C++/Java/Python 模拟硬件行为(如生成虚拟电压信号) 后台运行,用户不可见(如Tinkercad用WebAssembly编译运行)
电路可视化 图形化拖拽界面 构建虚拟电路(连线、元件选择) 拖入Arduino UNO板 → 连接LED到D13引脚 → 开始仿真

主流仿真工具

Tinkercad Circuits(最适合初学者)

参考视频教程

操作流程

① 浏览器打开 Tinkercad→ 创建电路设计

② 拖入虚拟Arduino(如UNO/ESP32)和元件(如LED、传感器)

用C++写控制代码(基于Arduino库) → 点击仿真按钮

1
2
3
4
5
6
7
8
9
10
11
12
// 示例:虚拟按钮控制LED
void setup() {
pinMode(2, INPUT); // 按钮接虚拟D2
pinMode(13, OUTPUT); // LED接虚拟D13
}
void loop() {
if (digitalRead(2) == HIGH) { // 按钮按下
digitalWrite(13, HIGH); // 亮灯
} else {
digitalWrite(13, LOW);
}
}

优势:无需安装,支持ESP32/ESP8266仿真(含Wi-Fi模拟)

Proteus(专业级仿真)

代码流程

① 在Arduino IDE用C++编译出HEX文件

② 在Proteus中导入HEX到虚拟MCU → 模拟传感器信号

适用场景:电机控制时序验证、ADC精度测试等精密仿真

fritzing

网址

Virtual Breadboard

VBB 电路仿真,仅支持win10系统

WOKWi

可以模拟包括ESP32在内的微控制器仿真

网址

基本电子元器件

  • 灯泡

  • LED

    与灯泡的不同在于有单向导通性,建议最大值为20mA,不烧坏LED也不影响寿命

  • 三色RGB发光二极管

    可以理解为三个LED合成一体,有共阳极和共阴极两种,发光是RGB三色不同比例的叠加

  • 滑动开关/多位开关/轻触按键

    多位开关: 多个滑动开关合并到一块

    轻触按键: 按下后状态改变,松开后恢复

    image-20250819163422059

    1a和1b 与 2a和2b是默认连通的,按下按钮就会全连通

  • 常见的电池

    • 纽扣电池 常见CR系列为锂锰扣式电池 提供一个3V的电压,常见的cr2032系列
    • 干电池 一种以糊状电解液来产生直流电的化学电池,标称电压均为1.5V,日常使用型号有1号、5号、7号
    • 9V电池 积层电池 主要用于仪表测学仪器供电 电池容量小,电压高,内部化学反应迟經的电电流小能使用很长时间
    • 水果电池 利用水果中的化学物质和金属片发生反应产生电能的一种电池,电压很低
  • 其他可以提供电压的设备

    • Arduino Uno R3 根据接线可以提供3.3V或5V的电压
    • 电源 能提供0~30V的电压,注意电流波轮是做了一个限流的作用而不是让你设置电流
  • 测量仪器

    • 万用表 用于测量电压,电流与电阻
    • 示波器
    • 函数生成器
  • 电位器 - 可调电阻

    改变接入电路的电阻阻值

  • 人体红外检测模块PIR

    在安防领域中,PIR探测器的全称就是Passive Infrared Deteetor,即被动式红外探测器或身体感应器

    三个引脚: 电源,接地,信号 如果探测到移动,信号会输出电流

  • 继电器

    一种电控制器件,可以理解成是用小电流控制大电流的一种自动开关

    image-20250819165016451image-20250819165134683

  • 示波器 可以将数据收集起来,以曲线形式显示

  • 蜂鸣器

    可以发出声音

    可以分有源和无源(是否有震荡源)蜂鸣器

    有源蜂鸣器给高电平会自动响,无源蜂鸣器需要给震荡的信号才能响

    Tinkercad仿真中提供的是有源蜂鸣器

    输入的pmw电压频率直接决定音调。频率越高,音调越高(声音越尖细);频率越低,音调越低(声音越低沉)

  • 舵机

  • 红外接收器

最简单的自保持电路

image-20250819170714766

舵机

舵机是一种位置(角度)伺服驱动器。它的核心功能是:接收一个控制信号,并精确地转动到并保持在那个指定的角度位置

当说“舵机”时,通常就是指那种三线、用PWM控制、内部集成了控制电路和电位器位置伺服电机。它是伺服电机大家族中最常见、最易于使用的一员

伺服电机: 拥有闭环控制的电机

与普通电机的区别:普通直流电机通常只能控制“正转/反转/速度”,而舵机可以控制“转到特定角度”(比如精确转到30度、90度、180度)

与步进电机的区别:舵机内部集成了电机、减速齿轮、控制电路和反馈系统(如电位器),形成了一个闭环控制,所以它通常比开环控制的步进电机精度更高、扭矩更大、更易用

红色是正极(通常4.8-6V),棕色是地线,橙色是PWM信号线(用于接收控制信号)

  • 切勿将舵机的VCC直接接到Arduino的5V引脚上,尤其是扭矩较大的舵机!Arduino板载的5V稳压器无法提供足够电流,可能导致板子重启或烧毁。
  • 务必使用外部电源(如电池组、独立的5V电源模块)为舵机供电,并将此外部电源的GND与Arduino的GND共地(连接在一起)

使用场景

舵机的“角度保持”特性使其非常适合需要精确定位的场景:

  • 机器人关节:机器人的手臂、腿、头部、手指的转动。
  • 航模/车模:控制飞机舵面(故名“舵机”)、汽车转向。
  • 摄像头云台:控制摄像头上下左右转动,进行追踪。
  • 智能门锁:控制锁舌的弹出和收回。
  • 喂食器/开关:作为机械臂,远程控制一个物理开关的按下或松开

控制原理

舵机的控制需要一种特殊的PWM信号,其特点是:

  • 频率:通常为 50Hz(即周期为20ms)。
  • 脉冲宽度(高电平持续时间):决定了舵机的角度。
    • 0.5ms 的脉冲宽度 -> 位置
    • 1.5ms 的脉冲宽度 -> 90° 位置
    • 2.5ms 的脉冲宽度 -> 180° 位置
1
2
3
4
5
xychart-beta
title "舵机角度与脉冲宽度关系图"
x-axis "脉冲宽度 (ms)" [0.5, 1.0, 1.5, 2.0, 2.5]
y-axis "转动角度 (°)" 0 --> 180
line [0, 45, 90, 135, 180]

脉冲宽度在0.5ms至2.5ms之间线性变化,舵机的角度就会在0°至180°之间相应变化

像Arduino这样的平台提供了现成的 Servo,它可以自动帮我们处理这些底层细节

注意事项

  1. 不要手动扭动:在断电时,不要用力强行手动旋转舵机轴,极易损坏内部的齿轮和电位器。
  2. 注意扭矩:选择舵机时要注意其扭矩(kg·cm) 参数,扭矩太小可能带不动你的机械结构。
  3. 供电要充足:务必使用外部电源供电,确保电源能提供足够的电流(单个舵机工作电流可达几百mA,动作时更大)。
  4. 机械限位:很多舵机(如180°舵机)有物理限位,编程时不要试图让它转到超出范围的角度(如myServo.write(200)),否则会听到齿轮“打齿”的噪音,长期会损坏舵机。

电子电路

上拉和下拉电阻

用于确定引脚状态

上拉和下拉电阻的核心目的,是为按键(一个机械开关)在未被按下时,提供一个确定的、稳定的电平状态(高电平或低电平),防止其引脚处于不确定的“浮空”状态,从而避免误触发和电路不稳定

为什么需要他们

原因是浮空状态的危害

当我们把按键连接到微控制器(如 Arduino, STM32, 51单片机)的 GPIO 引脚时,理想情况是:

  • 按键断开 -> 引脚读到一种明确电平(比如 3.3V)
  • 按键闭合 -> 引脚读到另一种明确电平(比如 0V)

但问题在于,当一个引脚什么都不连接(即按键断开时)时,它处于高阻抗状态,也叫“浮空”状态。这个引脚的电平是不确定的,极易受到周围环境(如静电、电磁干扰)的影响,会在高电平和低电平之间随机波动。微控制器会因此读到混乱的信号,导致程序误以为按键被频繁地按下和释放

上拉/下拉电阻就是为了解决这个“浮空”问题而存在的

特性 上拉电阻 (Pull-up) 下拉电阻 (Pull-down)
默认电平 高电平 (1) 低电平 (0)
有效电平 低电平有效 (按下为0) 高电平有效 (按下为1)
常见应用 更常见,因为很多 MCU 内部集成上拉电阻 同样可用,但内部集成下拉的情况较少
电路影响 在按键按下时,Vcc 通过电阻到 GND 形成回路,有微小电流消耗 同样有微小电流消耗
选择依据 取决于你的程序逻辑和 MCU 特性 取决于你的程序逻辑和 MCU 特性

image-20250828114508924

👆🏻上图中偏左的为上拉电阻,偏右的为下拉电阻

  1. 优先使用上拉电阻:因为绝大多数微控制器(如Arduino, STM32, ESP32)的GPIO引脚都内部集成了可软件开启的上拉电阻。你只需要在代码中配置一下,无需外接物理电阻,非常方便。
    • Arduino示例: pinMode(pin, INPUT_PULLUP);
    • STM32 HAL示例: GPIO_InitStruct.Pull = GPIO_PULLUP;
  2. 如果你的MCU没有内部下拉电阻,而你需要的逻辑又是“高电平有效”,那么就需要外接一个下拉电阻。

电阻值选择:通常选择 4.7kΩ 到 10kΩ 的电阻。阻值太大会无法有效拉平干扰;阻值太小则在按键按下时会产生过大的不必要的电流消耗(费电),并且可能烧坏端口

PWM引脚

Arduino板上端口数字前带~表示他可以输出pwm信号

PWM是如何产生的?

微控制器(MCU)内部有多个专门的硬件模块,叫做定时器/计数器。它们就像一个个独立的小时钟,可以精确地计数和计时。

  • 频率(Frequency):由定时器的计数速度决定。定时器计数得多快,一个PWM周期的完成就有多快,从而决定了PWM的频率。
  • 占空比(Duty Cycle):在同一个定时器内,有专门的比较寄存器。当定时器的计数值小于比较寄存器的值时,输出高电平;大于时,输出低电平。改变比较寄存器的值,就能改变高电平的时间比例,即占空比。

关键点:一个定时器可以同时控制多个输出通道(通常2到4个),为这些通道提供相同的计数基准(即相同的频率),但每个通道都有自己独立的比较寄存器(即可以有不同的占空比)。

为什么同一块板子上的PWM引脚频率会不同?

因为你的开发板(比如常见的Arduino Uno、STM32等)的MCU芯片内部有多个定时器,而不是只有一个。

  • 引脚分组:板子上的PWM引脚并不是平等的,它们被分组连接到不同的定时器上。

    例如,在Arduino Uno(基于ATmega328P)上:

    • 引脚 5 和 6 由Timer0控制,默认频率约 976 Hz。
    • 引脚 9 和 10 由Timer1控制,默认频率约 488 Hz。
    • 引脚 3 和 11 由Timer2控制,默认频率约 488 Hz。

这些定时器在出厂时或被Arduino库初始化为不同的默认值,所以它们产生的默认PWM频率就不同。

  • 你也可以自己改变频率:通过编程,你可以重新配置这些定时器(比如改变它的预分频器设置),从而改变其频率。但请注意:

    • 如果你改变了Timer1的频率,那么所有由Timer1控制的引脚(如Arduino的9和10脚)的频率都会一起改变。
    • 由一个定时器控制的多个引脚,频率必然相同,但占空比可以各自独立设置。

为什么要这样设计?(这样设计的好处)

这种设计主要是为了灵活性和功能分工,主要原因有以下几点:

  1. 兼顾精度与性能

    • 高频PWM:适合控制开关电源、LED调光(消除人眼可见的闪烁)等需要快速响应的场合。
    • 低频PWM:适合控制舵机(Servo)、生成音频信号等需要精确脉冲宽度的场合。Arduino默认用Timer1(16位定时器)来产生更稳定的低频PWM,就是因为它的精度更高,更适合驱动舵机。
  2. 独立控制,互不干扰

    • 假设你有一个项目,需要同时:
      • 用高频PWM控制一个电机的转速(比如25kHz)。
      • 用50Hz的PWM信号控制一个舵机。
    • 如果所有PWM引脚都必须是同一个频率,你就无法同时完成这两个任务。
    • 有了多个独立定时器,你就可以将Timer0设置为高频率控制电机,同时将Timer1设置为50Hz来控制舵机,两者互不干扰。
  3. 资源分配与优化

    • 芯片设计者提供了多个不同位宽(8位、16位)和功能的定时器,以满足各种不同的应用需求。让用户可以根据项目需要,灵活分配这些硬件资源。

总结

  • 不同频率:是因为它们背后连接的是不同的定时器硬件。
  • 同一频率:连接在同一个定时器上的多个PWM引脚,必须有相同的频率。
  • 为什么要这样:为了提供灵活性,允许用户为不同的任务(电机控制、舵机控制、LED调光等)同时设置不同的最佳频率,让各个功能模块互不干扰地工作。

image-20250901160005788

设置的引脚10写入“100”是什么意思?为什么是方波?

在Arduino编程中,使用 analogWrite(pin, value)函数来向标有~的PWM引脚写入值。这个 value的取值范围是 0 到 255

  • analogWrite(10, 0):表示占空比为 **0%**,引脚持续输出低电平(0V)。
  • analogWrite(10, 255):表示占空比为 **100%**,引脚持续输出高电平(5V)。
  • analogWrite(10, 100):表示占空比约为 **100/255 ≈ 39.2%。这意味着在一个PWM周期内,引脚只在39.2%的时间输出高电平(5V),其余60.8%**的时间输出低电平(0V)。

所以,您并没有命令引脚“一直输出高电平”,而是命令它“以大约39%的占空比快速开关”

输出电压 ≈ (100 / 255) × 5V ≈ 0.392 × 5V ≈ 1.96V

这正好完美匹配了您电压表上1.96V的读数! 电压表告诉你的是:“这个引脚输出的平均电压是1.96V”,而示波器告诉你的是:“这个引脚是通过快速在5V和0V之间切换来产生这个1.96V平均电压的”

1
2
//如果期望的是一条稳定的5V直线,不应该使用 `analogWrite`,而应该使用数字写入函数:
digitalWrite(10, HIGH); // 引脚持续输出5V,示波器上显示一条水平的直线,电压表显示5V
函数 digitalWrite() analogWrite()
输出信号类型 纯数字信号 (Digital) 脉冲宽度调制信号 (PWM - 并非真实模拟)
物理电平 HIGH= Vcc (如5V/3.3V) LOW= 0V (GND)
快速切换于 HIGH 和 LOW 之间
电压可变性 ❌ 只有2种固定电平 ✅ 平均电压可变 (0V ~ Vcc)
硬件依赖 任何通用I/O引脚 (GPIO) 仅限带 ~ 标记的PWM引脚 (需硬件定时器支持)

二者的应用场景对比

digitalWrite():纯二进制控制

场景 典型应用 为什么用?
开关类器件 - 继电器通断 只需“开/关”状态,无需中间值
- 晶体管/MOSFET开关
状态指示 - LED电源灯亮灭 视觉/听觉只需二元状态
- 蜂鸣器报警鸣叫
数字通信 - SPI时钟线(SCK) 协议要求精确的0/1跳变
- I²C起始信号
按钮读取 检测按键是否按下(HIGH/LOW) 输入本身就是数字信号

analogWrite():模拟效果仿真

场景 典型应用 PWM的优势
调光/调色 - LED呼吸灯 ✅ 无极调节亮度/颜色,无级变速平滑
- RGB灯颜色渐变
电机调速 - 直流电机转速控制 ✅ 精确控制功率输出,替代笨重的模拟电路
- 风扇风力调节
舵机角度控制 机器人关节舵机(50Hz PWM信号) ✅ 用脉冲宽度精确映射角度 (0°~180°)
简易音频合成 生成方波音乐(如8-bit游戏音效) ✅ 频率可调,占空比改变音色
加热功率控制 电热丝/加热片温控 ✅ 高效调节发热功率
  • digitalWrite是控制世界的开关
  • analogWrite是调节万物的旋钮
  1. PWM是“假”模拟
    Arduino Uno/Nano 没有真正的DAC(数模转换器)。analogWrite()只是通过PWM高速开关模拟出中间电压效果。需外接DAC芯片才能输出真实连续电压(如音频输出)。

  2. 分辨率限制
    标准Arduino PWM为8位 (0-255共256级),精细度有限。STM32等高端MCU可达16位(0-65535),精度提升256倍!

  3. 引脚冲突风险
    修改PWM频率(如 TCCR1B寄存器)会影响同一定时器的所有引脚!例如改Timer1频率后,Pin9和Pin10行为会同时变化。

  4. 驱动能力≠输出类型
    无论digitalWrite还是analogWrite,最大输出电流仅约40mA。驱动大功率设备(电机/灯带)必须加晶体管/MOSFET驱动电路!

PWM引脚的”bug”

以ARDUINO UNO板子为例,会有下面的情况:

  • 舵机影响9和10引脚pwm
  • 蜂鸣器和红外接收影响3和11引脚pwm

原因是: Arduino UNO (基于ATmega328P芯片) 只有3个独立的硬件定时器

Arduino UNO的PWM引脚是这样分配给各个定时器的:

  • Timer0 -> 控制引脚 56
  • Timer1 -> 控制引脚 910
  • Timer2 -> 控制引脚 311

一些复杂的库(如舵机库Servo.h、红外接收库IRremote.h)为了实现其功能,需要接管(Reconfigure) 整个定时器。

  • **舵机库 (Servo.h) 与 引脚9和10 (Timer1)**:
    • 舵机需要非常精确的50Hz PWM信号。
    • 为了方便和保证精度,Servo.h库默认会接管16位的Timer1,将其强制设置为产生50Hz的PWM模式。

更强大的板子(如ESP32, STM32, Arduino Mega)情况好得多

例如,ESP32有高达16个定时器,STM32F4系列有14个以上,Arduino Mega有6个

更简单的板子(如ATTiny85)情况更糟

ATTiny85,只有1~2个定时器

模拟引脚

  • 在Arduino等开发板上,通常标有 **”A0”, “A1”, “A2”…**。

  • 其背后连接的是一个叫做 ADC (Analog-to-Digital Converter,模数转换器) 的硬件模块

  • 模拟引脚 (Analog Input Pins) -> 负责读取外部的模拟世界(如温度、光线强度)。

  • PWM引脚 (Digital PWM Pins) -> 负责模拟输出一个中间值,去控制外部设备。

特性 模拟输入引脚 (A0, A1, A2…) PWM引脚 (D3, D5, D6, D9, D10, D11 ~)
核心功能 读取外部模拟电压值 输出脉冲宽度调制信号
信号类型 输入 输出
工作方式 ADC (模数转换器) 定时器产生特殊方波
测量/控制对象 物理世界的连续变化量 输出效果的“平均强度”
分辨率 10位 (0-1023) 8位 (0-255)
返回值/参数 int value = analogRead(A0); analogWrite(9, value);
典型应用 读取电位器、光敏电阻、温度传感器 控制LED亮度、电机转速、舵机角度

原理

模拟引脚的实现依赖于一个叫做 ADC (Analog-to-Digital Converter,模数转换器) 的硬件模块。它的任务是将一个连续的模拟电压信号,转换成一个离散的数字数值,以便MCU的数字核心能够读取和处理

当你执行 analogRead(A0)时,底层硬件悄然完成以下操作:

  1. MCU内部的多路选择器将A0引脚连接到ADC模块。
  2. ADC启动一次转换。
  3. 采样保持电路捕获A0引脚上的瞬时电压。
  4. ADC进行量化,将电压值转换为0-1023之间的数字。
  5. 转换完成,结果被存入数据寄存器。
  6. analogRead()函数从这个寄存器中取出这个值并返回给你的程序。

详细如下:

  1. 采样 (Sampling)

    ADC并不是持续不断地测量电压,而是以一个固定的时间间隔(由采样频率决定),快速地“瞥一眼”输入引脚上的电压值,并把这个瞬间的电压值捕获下来

采样率 (Sample Rate)。根据奈奎斯特采样定理,采样频率必须至少是输入信号最高频率的2倍,才能无失真地还原原始信号。但对于大多数Arduino测量的传感器(如缓慢变化的温度、光线),默认的采样率已经足够

  1. 量化 (Quantization)

    捕获到的模拟电压值可以是某个范围内的任意值(如3.1415926… V)。但ADC需要把它“归类”到一个有限的、离散的等级中去。这就是分辨率的概念

    分辨率 (Resolution),通常用位数 (bits) 表示。

    • Arduino UNO的ADC是 10位 的。这意味着它可以将 0V至5V 的参考电压(Vref)划分为 2¹⁰ = 1024 个离散的等级(从0到1023)。
    • 每个等级代表的电压差是: 5V / 1024 ≈ 0.00488V (4.88mV)
  2. 编码 (Encoding)

    将量化后得到的等级数值,转换成一个二进制数字,并存储到MCU的寄存器中,等待程序通过 analogRead()函数来读取

    最终在代码中得到的 int sensorValue = analogRead(A0);里的 sensorValue,就是这个编码后的数字值(0-1023)

相关参数

  1. 分辨率 (Resolution)

    • 定义:ADC能区分的最小电压变化能力,位数越高,分辨率越高。
    • 举例
      • 10位 (Arduino UNO):范围 0-1023,最小电压步进 ~4.88mV。
      • 12位 (STM32, ESP32):范围 0-4095,最小电压步进 ~1.22mV(假设5V参考电压)。精度是10位的4倍!
    • 影响:分辨率决定测量的精细度,高分辨率ADC能探测到更微弱的电压变化。
  2. 参考电压 (Reference Voltage, Vref)

    • 定义:ADC用于比较和量化的基准电压,通常是MCU的工作电压(如Arduino UNO为5V)。
    • 重要性:测量小电压范围(如0-1V)时,若使用5V参考电压,精度会大幅浪费。
    • 解决方案:许多MCU允许改变参考电压,使用analogReference()函数可改变Arduino的参考电压,提高精度。
  3. 采样速度 (Conversion Speed)

    • 定义:ADC完成一次采样-量化-编码过程所需时间。
    • 影响:决定能测量多快变化的信号,捕获音频信号(20kHz)需高采样速度(>40kHz),而测量温度等缓慢变化的数据,低速ADC也足够。