本章不写代码。它回答四个"为什么":
①芯片为什么会听 0 和 1 的话?②0 和 1 在电路里长什么样?
③程序是怎么被芯片"记住"并"执行"的?④烧录/编译/运行到底分别做了什么?
每一节都从最底层讲起,把概念彻底拆开。看懂这一章,
后面所有章节里的名词都不会再"悬空"。
本章只是打地基、不动手;等到第 2 章真正接线动手前,请先翻一遍 0.10 的安全红线。
0.1 电的基础:电压、电流、GND
电压 = 水位差
把电想象成水管里的水。要让水流动,两个位置要有"高度差"——这就像电压。
电压的单位是伏特(V)。它衡量的是"两个点之间的电势差"。
- 3.3V:A 点比 B 点高 3.3 伏。
- 0V:两个点一样高,没有"推水"的力。
电流 = 水流量
水从高处流向低处,就是电流(单位:安培 A)。电流是"电子的流动"。
电流永远从高电压流向低电压(就像水往低处流)。
GND = 共同的"0 米海平面"
电路需要一个统一的"0 米基准",叫GND(Ground,地)。所有电压都是
“相对于 GND 说的”。一块板子只有一个 GND。
共地= 两块板子的 GND 用一根线连在一起,让它们有同一个海平面。
- 共地了:A 板的 3.3V 和 B 板的 GND,才有确定的 3.3V 差,信号才准确。
- 不共地:A 板说"高",B 板不知道"相对谁高"——信号就乱了。
这是新手第一大坑,第 8 章会再次出现。
0.2 为什么芯片只懂 0 和 1?(从晶体管讲起)
晶体管:一个"电子开关"
芯片里最基本的零件叫晶体管(transistor)。你可以把它理解成
一个用小电信号控制通断的开关:给控制脚一个小信号,它就决定另一条路上
的大电流是通还是断(L298N 内部就是这种东西,第 8 章细讲):
控制脚 ──► ══开关══- 控制脚电压高 → 开关闭合(导通);
- 控制脚电压低 → 开关断开(截止)。
一个开关只有两种状态:通/断。用1 表示通、0 表示断。
这就是"0 和 1"的物理来源——它不是一个抽象概念,而是电路里真实存在的
“通"和"断”。
逻辑门:把开关组合成"会算数的电路"
几个晶体管组合起来,就能实现"如果 A 和 B 都通,输出才通"这类规则,
这叫逻辑门(logic gate):
- 与门 AND:A 和 B 都是 1,输出才 1;
- 或门 OR:A 或 B 有一个是 1,输出就是 1;
- 非门 NOT:输入 1,输出 0(反过来)。
亿万个逻辑门组合在一起,就成了 CPU(能算数)、内存(能记住)、
和各种外设(能控制引脚)。
结论:芯片听不懂中文,但它内部全是由"通/断"开关组成的电路。
所以我们用 0 和 1 跟它说话,它就能直接"听懂"。
0.3 数字信号在电路里长什么样?——电平
芯片的引脚输出"1"和"0",实际是输出电压:
| 数字 | 电压 | 名字 |
|---|---|---|
| 1 | 约 3.3V | 高电平 HIGH |
| 0 | 约 0V | 低电平 LOW |
电平不是"绝对电压",是"范围"
接收方判断高低,不是量到正好 3.3V,而是看电压落在哪个范围:
3.3V ┤■■■■■■■■ 高电平区(认为读到 1) │ ↑ 阈值(约 1.5~2V 左右;这是简化模型,精确的双阈值模型见 4.1) 0V ─┴■■■■■■■■ 低电平区(认为读到 0)- 高于阈值 → 读到 1;
- 低于阈值 → 读到 0。
- 这里的阈值就像考试的及格线:分数(电压)到了线就算"过"(读成 1),
没到就算"不过"(读成 0),接收方只认这条线,不去量精确电压。而电压
越过这条线的瞬间,就是下一节要讲的"沿"。 - 只要不落在中间"模糊区",信号就是可靠的。这就是数字信号抗干扰的原因:
一点点噪声不会让它从"高"变成"低"。
ESP32 引脚电平
这里说的"引脚",全书也常写成GPIO(General Purpose Input/Output,
通用输入输出脚)——就是芯片外壳上那一根根你能自由读写的金属脚,
编号 GPIO0、GPIO1……本书很多实验就是在这些脚上收发电平。
- 输出:高电平 3.3V,低电平 0V(不能输出 5V);
- 输入:能承受的电压上限约 3.3V(部分引脚 5V 容忍)。5V 直接接普通 GPIO
会烧芯片——因为输入电路是按 3.3V 设计的。
所谓"5V 容忍(5V-tolerant)",是指少数加固过的引脚即使输入 5V 也不会烧,
但普通引脚不在此列——拿不准就一律当成"不能碰 5V"最安全。
0.4 电平的"变化":上升沿与下降沿(重要概念!)
信号不只是"停在高/低",还会变化。这个变化过程有两个关键"时刻":
电压 3.3V ┤ ┌────────┐ │ │ │ ← 这段平台期间电平一直为高,叫"高电平期间" │ │ │ 0V ─┴────────┘ └──────── ↑ ↑ 上升沿 下降沿 (低→高的瞬间)(高→低的瞬间)上升沿(Rising Edge)
信号从低变高的那个瞬间——电压从低电平区跨越阈值、进入高电平区。
下降沿(Falling Edge)
信号从高变低的那个瞬间——电压从高电平区跌回低电平区。
为什么"沿"这么重要?(关键逻辑)
"沿"是一个瞬间事件,电平是一个持续状态。区别在于:
- 电平触发:只要电压是"高",就一直在"触发"(像一直按着门铃);
- 边沿触发:只在"变高的那一瞬间"触发一次(像按一下门铃响一声)。
硬件里有很多"只认沿"的机制,例如:
- 中断:按钮按下产生下降沿,芯片只在沿上进入中断一次
(中断的注册、ISR 规则、与任务通信的完整讲解,见第 15 章)。
打个比方:中断就像你正在写作业时突然来电铃响——你会放下笔先去接电话,
接完再回来继续写。芯片也一样,手头活先暂停,跳去处理这件"急事"(这段
临时去干的代码就叫 ISR,Interrupt Service Routine,中断服务程序)。 - 计数器:每次上升沿数一次数(数脉冲);
- 同步电路:芯片内部所有逻辑都在时钟的上升沿统一"拍板"
(见 0.5)。
记法:电平 = 状态,沿 = 变化的一瞬间。看时序图时,
“上跳沿”=上升沿,“下跳沿”=下降沿。
第 4 章我们会实际遇到"抖动"——那是沿在短时间内反复出现多次。
0.5 时钟:芯片内部的"节拍器"
芯片里的动作不是随便乱来的,而是按照一个时钟(clock)信号,
一拍拍地做。时钟就是一个非常稳定的方波(一种在高、低两级之间
规则切换、画出来像一排正方形的波形,见下),每秒跳很多次:
高 ┤┌┐ ┌┐ ┌┐ ┌┐ │││ ││ ││ ││ 低 ─┘└─┘└─┘└─┘└─► 时间 ↑↑ ↑↑ 每两次上升沿之间 = 1 个时钟周期- 时钟每秒跳的次数叫频率,单位Hz(赫兹)。1Hz = 每秒 1 拍。
- 我们芯片的 CPU 时钟是160MHz= 每秒跳 1.6 亿拍。反过来算,一拍只占
1 ÷ 160,000,000 秒 = 6.25 纳秒(ns)——1 纳秒是十亿分之一秒。 - 芯片内部逻辑(加法、比较、搬运)在每个时钟上升沿统一执行一步。
理解时钟的意义:它让"亿万个开关"步调一致,就像一支乐队跟着
指挥的拍子演奏。没有时钟,信号会乱。操作系统也靠时钟:FreeRTOS(跑在芯片上的一个小型实时操作系统,
负责把"多件同时要做的事"排好队、分时间片轮着干)用时钟节拍(tick)
决定"下一个该谁跑",让多个程序轮流使用同一个 CPU——第 13 章展开。
0.6 寄存器:芯片里"能记住数"的小格子
【十六进制小前置】本节开始要频繁看到
0x开头的地址(如0x60004004)。0x是"十六进制"的记号:逢 16 进一,用 0~9 和 A~F 共 16 个符号写一个数位,
每 1 个十六进制位刚好对应 4 个二进制位。它只是把一长串 0/1 缩写得更短,
和十进制是同一个数的不同写法(就像0x41= 65 = 二进0100 0001)。
现在只要认出"0x开头=十六进制、代表一个数或一个地址"就够用,
详细换算第 1 章 1.1 手把手教。
第 1 章会反复用到一个词:寄存器(register)。现在讲清它的本质。
每个寄存器 = 一组能记住 0/1 的电路单元(叫触发器,flip-flop)。
- 8 个触发器排在一起,就能记住一个 8 位数字(0~255);这里的一个"位"就是
bit(binary digit 的缩写,一个 0 或一个 1,是最小的一格信息)。 - 每个寄存器在芯片内部有一个地址,CPU 可以往它"写"或"读"。
外设寄存器:GPIO、PWM(Pulse Width Modulation,脉冲宽度调制——
用"高电平占多少时间"的比例来表示强弱,比如让灯半亮、让电机半速,
第 7 章细讲)这些硬件模块,它们的"控制旋钮"就是寄存器。
比如"让 GPIO2 输出高",本质是"往地址 0x60004004 的某一位写 1"。
(不同芯片的外设基址不同:0x3F40… 是初代 ESP32的地址段,
本书用的 ESP32-S3 外设寄存器在0x6000…段——别拿旧芯片的地址对号入座。)
ESP-IDF 的驱动函数(如gpio_set_level)就是封装好的"往寄存器写位"的动作。
你可以不用手写寄存器,但要理解:函数背后就是寄存器读写。
我们写的代码 │ gpio_set_level(GPIO_NUM_2, 1) ▼ 驱动函数:算出引脚对应的寄存器地址和位号 ▼ 往地址 0x60004004 的 bit2 写 1 ▼ 硬件电路看到该位为 1 → 输出高电平 3.3V0.7 程序是怎么"被记住"和"被执行"的?
存储:Flash 里躺着一串指令
我们写代码 → 编译成"芯片指令"(每条指令是一个数字,比如0x1100 0x2F00)→ 存进Flash(掉电也不丢的存储)。
固件就是这一整串指令。
执行:CPU 的"取指-译码-执行"循环
开机后 CPU 从 Flash 一条条读指令:
① 取指(Fetch):从 Flash 取出下一条指令 ② 译码(Decode):翻译成"要做什么"(加法?搬数据?) ③ 执行(Execute):真的去做 ④ 回到 ①(PC 指针指向下一条)CPU 里有个叫PC(程序计数器)的小寄存器,记着"下一条指令在哪"。
遇到跳转指令(if/循环/函数调用),PC 会被改到别的位置。
所以"程序在跑"= CPU 在不停地"取指-译码-执行"。
日志里的Calling app_main()意思是:CPU 已经把 PC 指到了
我们app_main函数的第一条指令。
内存映射:Flash、SRAM、外设都住在"一张地址表"里
芯片把所有东西(Flash、SRAM、寄存器)编上统一的门牌号(地址),
CPU 按地址访问。这就像小区里每栋楼都有门牌号:所谓映射,就是
“给远处仓库里的东西预先编好本地门牌号”——哪怕实物在别处,CPU 只要喊门牌号
(地址)就能找到它,不必关心它真正住在哪。
| 地址范围 | 住着什么 |
|---|---|
| 0x3FC8… | 内置 SRAM(草稿纸) |
| 0x4200… | Flash(课本,代码在这里) |
| 0x6000… | 外设寄存器(旋钮们) |
| 0x3C00… | 外部存储映射窗口(Flash 数据 / PSRAM 都从这里映射进来,PSRAM 就是"大仓库") |
0.8 编译、烧录、运行:三件事分别做了什么
| 步骤 | 在做什么(底层) | 产物 |
|---|---|---|
| 编辑 | 你写人话(C++) | 源文件 .cpp |
| 编译 | 翻译官把 C++ 变成芯片指令 | 目标文件 .o → 链接 → .elf |
| 打包 | 按烧录格式整理成 .bin | .bin 文件 |
| 烧录 | 通过 USB/串口把 .bin 里的字节写进 Flash 的对应地址 | Flash 里的数据 |
| 运行 | 芯片复位 → 引导程序启动 → CPU 取指执行 | 芯片在干活 |
烧录的底层过程(知道大概即可)
- 电脑用 esptool 通过串口和芯片里出厂自带的ROM 引导程序说话;
- 让芯片进入"下载模式"(所以有些板子要按 BOOT 键);
- 把 .bin 按地址一块块写入 Flash;
- 写完校验(
Hash of data verified),复位芯片开始运行。
这是"有线"烧录。产品上线后,固件更新通常走网络下载,叫
OTA(空中升级),原理与安全要求见第 18 章。
开机启动链(日志里看到的每一步)
① ROM 引导:芯片出厂自带的程序(无法改写) ESP-ROM:esp32s3-20210327 ② 二级引导:加载真正的固件(IDF 自带) boot: ESP-IDF v6.0.1 2nd stage bootloader ③ 初始化:认出 Flash/PSRAM/CPU,分配内存 octal_psram: Found 8MB PSRAM device cpu_start: cpu freq: 160000000 Hz ④ 进入我们的程序 main_task: Calling app_main()看到Calling app_main(),说明烧录成功、程序真的在跑了。
之后日志里再有报错,是程序逻辑问题,不是烧录问题——这两件事
必须分清(这也是本书最初闹的误会)。
0.9 串口:芯片和电脑怎么"打电话"
底层原理:一高一低,一秒传多少位
串口(UART)通信就是一根线轮流变高变低,把 0 和 1 一个一个传过去。
电脑 ──TX──► RX 芯片 (TX=发送脚,RX=接收脚) 电脑 ◄──RX── TX 芯片- 双方约定波特率(baud rate):每秒传多少位。我们全用115200。
可以验算:一帧 = 1 起始位 + 8 数据位 + 1 停止位 = 10 位,
115200 ÷ 10 = 每秒约 11520 个字符——发一行 60 字的日志只要几毫秒。 - 传一个字符(如字母 A = 0x41 = 0100 0001)要按帧来:
这里0x41(十六进制)与0100 0001(二进制)是同一个数的两种写法,
只是缩写长度不同,第 1 章 1.1 会把这两种写法互相换算讲透。
空闲(高) 起位 数据位(LSB在前) 停止位 ──────┐┌──────┬──┬──┬──┬──┬──┬──┬──┬──┬──── ││ 0 │ 1 │ 0 │ 0 │ 0 │ 0 │ 1 │ 0 │ 0 │ 1 │ ← 0x41 加上起止位 └┘ ↑ 开始 ↑ 8 个数据位(bit0 先发) ↑ 停止注意数据位的顺序:0x41 = 0b01000001,最低位(bit0 = 1)先发,
所以线上依次是1 0 0 0 0 1 0 0——你收到的第一个数据位其实是 bit0,
而不是肉眼最容易认的"最左边那位"。收发双方都默认这个顺序,才能对上。
- 起始位:线从高变低(一个下降沿),告诉对方"要开始了";
- 数据位:8 个 0/1;
- 停止位:回到高,一个字符结束。
这就是"串口"名字的来历:**一个比特接一个比特(串行)**地传。
对比"并行"(8 根线同时传 8 位):串口省线但慢。
日志就是通过串口"喊"出来的
ESP_LOGI(TAG, "L298N 直流电机演示启动")把文字转成字符,再按上面的帧格式
从 USB 串口发到电脑,监视器(monitor)收到后显示在屏幕上。
看日志 = 读芯片通过串口发来的话。
日志只是串口的"单向喊话"。串口还能与模块(GPS、串口屏、AT 指令设备)
双向收发数据——编程用法见第 14 章。
0.10 安全须知(动手前的红线)
- 先断电再接线:改线时电路处于未知状态,带电操作可能短路烧件。
- 共地:开发板 GND 与驱动板 GND 必须相连(0.1 的原理)。
- 电源别接反:正负极接反,电流方向反了,芯片/驱动板可能烧毁。
- 别用 GPIO 直接带电机:GPIO 只能输出几毫安,电机要几百毫安甚至
几安培——不是"力气小"的问题,而是电流不够会烧坏引脚输出电路。
电机必须经过驱动板(第 8 章)。 - 5V 别接普通 GPIO:输入电路按 3.3V 设计(0.3)。
0.11 遇到问题怎么办(底层思路)
- 分层定位:问题在"电源层/接线层/烧录层/程序层"哪一层?
- 没电、没共地 → 电源/接线层;
- 烧不进 → 烧录层;
- 烧进了但行为不对 → 程序层。
- 看日志:芯片自己会报。绝大多数问题日志直接给出线索。
- 看完整报错:报错文本里的函数名、地址都是线索(第 11 章教翻译)。
- 搜索:把报错里最独特的词 +
esp32搜,全世界都有人踩过。
0.12 想一想 + 小测验
想一想:
- 为什么芯片只用 0 和 1?请用"晶体管只有通/断两种状态"来回答。
- 上升沿和"高电平"有什么区别?
- 看到日志
Calling app_main()能说明什么?不能说明什么?
小测验:
- 电压的比喻是"水位差",那么电流的比喻是____。
- 信号从高变低的那一瞬间叫什么?(A. 上升沿 B. 下降沿 C. 高电平)
- 串口里"起始位"的作用是什么?
- 程序被 CPU 执行的三个步骤按顺序是:、、____。
0.13 本章总结
- 0 和 1 = 晶体管的通/断,芯片全由这种开关组成;
- 电平 = 电压范围(3.3V 高、0V 低),沿 = 变化的瞬间;
- 时钟 = 节拍器,让所有逻辑统一行动;
- 寄存器 = 能记住数的电路,控制硬件就是写寄存器;
- 固件 = Flash 里的指令,CPU 取指-译码-执行;
- 串口 = 一比特一比特传 0/1,日志就是通过它喊出来的。
下一章,我们学写代码的语言 C++——它最终会变成这一串串 0 和 1。