工科学习笔记STUDENT TECH NOTES
在校学生 · 学习笔记 非商业 · 无广告 持续更新

把电路、代码和公式,
讲明白。

这里是我课余整理的技术笔记:从 PCB 画板与打样、单片机外设与定时器,到 运动解算 与 闭环 PID 控制。 能画出来的就画成图,能推导的就把公式写清楚,能动手验证的就写个仿真或做个实物。

● 图解 + 公式 + 可交互仿真 ● 记录踩过的坑与解决过程 ● 内容有误欢迎指正
MCU 1) 叠层 · 走线 · 回流 θ1 θ2 2) 正解 / 逆解 目标 3) 闭环 PID 响应 LEARNING NOTES 图 0 · 我的四个学习主题
01 / 学习地图

我是按这个顺序学的

顺序不是绝对的,但每往前一步,都会让后面少踩一些坑。比如先懂器件和电源,画板时才知道为什么要放去耦电容;先让单片机跑起来,才谈得上采样和控制。

STEP 01

电路与模电基础

看懂电阻电容电感、三极管与运放的基本用法,会用万用表测通断和电压。

STEP 02

PCB 设计与打样

画原理图、做封装、布线与检查 DRC,然后下单打样,等板子的这几天顺便看手册。

STEP 03

单片机与外设

点亮 LED 只是开始,重点是 GPIO、定时器、串口、ADC 这些外设背后的时序。

STEP 04

通信与调试

UART / I2C / SPI / CAN,配合逻辑分析仪看波形,学会从波形判断问题出在哪。

STEP 05

控制与算法

从开环到闭环,理解 PID 三项各自在做什么,再学滤波、采样与离散化。

STEP 06

综合项目

把上面几块拼起来做一个完整的东西:平衡车、循迹小车、小机械臂等等。

02 / PCB 设计

画板子,其实是在给电流修路

原理图只是“谁连谁”,PCB 决定了“电流怎么走”。同一张原理图,两个走线方案可能一个安静稳定,另一个一上电就复位、通信就丢包。

先理解叠层与回流路径

电流永远走回路。信号线从芯片流到器件,再沿着参考平面(通常是 GND 平面)流回芯片。这条回流路径越贴近信号线,环路面积就越小,辐射和干扰也就越小。

所以四层板常见的叠层是信号—地—电源—信号,把两个内层当作完整的参考平面。这也是为什么布线时最怕的是:在平面正下方开了一道长长的槽,把回流的路剪断了。

要点
去耦电容的位置比容量更重要:0.1 µF 要尽量贴着芯片的电源引脚,10 µF 放在附近负责中低频。距离一远,引线电感就把电容的高频特性毁掉了。
口诀
电源走宽、地要连续、晶振贴近、高速等长、模拟远离开关电源。
丝印层 阻焊层(Solder Mask) 顶层铜 · 信号 35 µm FR4 介质(约 0.2 mm) 内层铜 · GND 平面(完整) FR4 芯板(约 0.5 mm) 内层铜 · VCC 平面 FR4 介质(约 0.2 mm) 底层铜 · 信号 35 µm 阻焊层 丝印层 总厚约 1.6 mm
图 01 · 四层板叠层剖面:两块内层铜做成完整的 GND / VCC 参考平面,顶层与底层跑信号。

回流路径:好与坏

✓ 参考平面完整 信号线 GND 平面 回流紧贴信号线正下方 环路面积小 → 电感小、辐射小、抗干扰强
图 02a · 平面完整时,回流电流就在信号线正下方,环路面积最小。
✗ 平面被开槽 信号线 开槽 回流被迫绕行 → 环路面积大、噪声与 EMI 上升
图 02b · 平面被开槽时,回流只能绕远路,环路面积变大,相当于给电路加了一副“天线”。

布局布线清单

LAYOUT 01

先分区,再布线

电源、功率、模拟、数字分区摆放,接口靠近板边,让大电流路径短而宽。

LAYOUT 02

晶振要“贴脸”

晶振紧贴芯片 XIN/XOUT,走线尽量短、下方不走其他信号线,负载电容按手册选。

LAYOUT 03

模拟信号小心走

采样信号远离时钟与开关电源,尽量差分走线或两侧包地,ADC 参考电压单独滤波。

LAYOUT 04

过孔也是元件

一个过孔大约 1 nH 电感。高速线上少打孔,换层时旁边补回流地过孔。

LAYOUT 05

接口要“耐造”

外部接口加 TVS / ESD 管、限流电阻与滤波,避免拔插和静电直接打到芯片引脚。

LAYOUT 06

方便调试

关键电源网络留测试点,串口与 SWD 引出排针,丝印标清引脚定义和版本号。

踩坑
第一次打样,我把某个芯片的封装引脚顺序弄反了,焊上去完全不工作,查了两天才发现是封装镜像问题。出板前打印 1:1 图纸,拿实物芯片比着对一遍引脚。
踩坑
丝印压住焊盘,贴片时锡膏流到丝印上导致虚焊;0402 的电容手焊时经常被烙铁“粘走”。手板阶段优先用 0603,好焊、好修、好测量。
03 / 单片机

难点不在写代码,在于知道时序

外设库把寄存器包起来了,但硬件行为没变:定时器怎么计数、PWM 什么时候翻转、中断标志什么时候清,这些决定了程序稳不稳。

MCU CORTEX-M 3.3 V 电源 LDO + 0.1µF + 10µF 8 MHz 晶振 2 × 20 pF 负载电容 复位电路 NRST + 0.1µF + 10k SWD 下载调试 SWDIO / SWCLK / GND BOOT0 选择 跳线帽 / 下拉电阻 去耦电容组 每个电源脚一个 0.1µF 状态 LED 串联 1 kΩ 限流
图 03 · 单片机最小系统:电源、晶振、复位、下载口这四块齐了,芯片才能真正“活”起来。

上电之后发生了什么

  • 复位向量:上电后 PC 跳到启动文件里定义的中断向量表首地址。
  • 时钟初始化:配置 HSE/HSI 与 PLL,把系统时钟切到目标频率——串口乱码十有八九出在这一步。
  • 初始化外设:GPIO、定时器、串口、ADC、DMA 逐个配置寄存器。
  • 进入主循环:轮询任务,或者交给 RTOS 调度;中断负责处理“来不及等”的事件。
提示
程序“进去就卡住”时,先确认时钟配置是否正确、等待标志位的循环有没有加超时,再看是不是中断标志没清导致反复进中断。

定时器与 PWM:让输出精确可控

定时器本质是一个不断累加的计数器。计数到 ARR(自动重装载值)时归零并产生更新事件;把比较值 CCR 拿出来和计数器比较,就能做到“数到 CCR 之前输出高、之后输出低”,这就是 PWM。

fPWM = fCLK / [ (PSC + 1) × (ARR + 1) ]
PSC 是预分频系数。例如 72 MHz 时钟、PSC = 0、ARR = 3599,得到的 PWM 频率正好是 20 kHz。
占空比 D = CCR / (ARR + 1)
ARR = 3599 时,CCR = 1800 对应约 50% 占空比。改占空比只需要改 CCR,不用重新配置定时器。
选频率
驱动电机常用 10–20 kHz:高于人耳可听范围(听不到啸叫),又不至于让 MOS 管开关损耗太大。驱动舵机则用 50 Hz、脉宽 0.5–2.5 ms。
CNT PWM UEV ARR CCR 高 低 一个 PWM 周期 计数归零 → 更新中断置位,可在这里累加计时变量 占空比 = 高电平时间 / 周期 = CCR / (ARR + 1) 互补输出加死区,可以避免 H 桥上下管同时导通
图 04 · 定时器计数到 ARR 归零,比较值 CCR 决定这段时间内高低电平的比例。
tim_pwm.c · 定时器 PWM 初始化
/* 系统时钟 72 MHz,PWM 20 kHz:PSC=0, ARR=3599 */
htim3.Init.Prescaler = 0;
htim3.Init.Period    = 3599;      // ARR
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);

// 改占空比:50%
__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, 1800);
isr.c · 1 ms 定时中断的写法
volatile uint32_t tick = 0;

void TIM2_IRQHandler(void) {
  if (TIM2->SR & TIM_SR_UIF) {
    TIM2->SR = ~TIM_SR_UIF;   // 先清标志,否则会反复进中断
    tick++;                   // 中断里只做最短的事
  }
}
踩坑
在中断里调用 HAL_Delay() 或往串口 printf,中断频率一高主循环就再也跑不动。中断只置标志,重活留给主循环。
踩坑
主循环和中断共享的变量没加 volatile,编译器优化后主循环读到的永远是旧值;共享的多字节变量还要用临界区(关中断)保护。
经验
M0/M0+ 内核没有浮点单元,控制环里大量浮点运算会吃掉不少时间。可以用定点数(Q 格式)代替,或者把计算放到低优先级任务里。
04 / 通信总线

会看波形,问题就少一半

通信调不通的时候先别急着改代码。拿逻辑分析仪抓一段波形,看起始条件、时钟极性、地址位、ACK 位对不对,通常几分钟就能定位。

四种常见总线的对比(按我自己的使用经验整理)
线数常见速率拓扑典型场景最容易踩的坑
UART2(TX/RX)9.6 k – 1 M点对点调试打印、蓝牙/WiFi/GPS 模块波特率误差、不共地、TX/RX 交叉
I²C2(SCL/SDA)100 k – 400 k多主多从(地址寻址)传感器、EEPROM、低速外设忘记上拉、地址写错、总线被拉死
SPI4(CS/SCK/MOSI/MISO)1 M – 50 M一主多从(片选)屏幕、Flash、高速 ADCCPOL/CPHA 不匹配、片选时序
CAN2(CAN_H/L)125 k – 1 M多主总线电机驱动器、车载、工业设备终端电阻缺失、ID 冲突、波特率不符

时序长什么样

UART TX 起始位 数据 8 位(LSB 在前) 停止位 空闲为高,起始位拉低,停止位拉高,一帧共 10 位(不含校验) I²C SCL SDA 起始条件 地址 + 读写位 数据 ACK SCL 为高时 SDA 由高变低 = 起始条件;SCL 为高时 SDA 必须保持稳定 SPI CS SCK MOSI CS 拉低选中从机;CPOL=0 时 SCK 空闲为低,数据在上升沿被采样
图 05 · 三种总线的时序:UART 靠起始/停止位定帧,I²C 靠起始条件和地址寻址,SPI 靠片选与时钟极性/相位。
波特率误差 = ( f实际 − f目标 ) / f目标 × 100%
一帧 10 位,接收端在每位中间采样。累计误差超过半个位宽就可能采错,所以一般把误差控制在 2%–3% 以内。误差主要来自系统时钟与分频系数不能整除。
I²C
SCL/SDA 是开漏输出,必须外接上拉电阻。3.3 V、400 kHz 常用 2.2–4.7 kΩ;上拉太大边沿变缓,太小则低电平灌电流过大。
CAN
总线两端各接一个 120 Ω 终端电阻吸收反射。断开电源后用万用表测 CAN_H 与 CAN_L,阻值应接近 60 Ω。
踩坑
SPI 屏幕点不亮,多数是 CPOL/CPHA 模式与从机手册不一致,或者片选在数据传输结束前就被拉高。
串口乱码怎么查? ① 确认两边波特率一致 ② 抓波形量一个位宽,算实际波特率 ③ 检查系统时钟与分频是否整除 ④ 确认共地,TX 接 RX ⑤ 看电平标准是否匹配(3.3 V / 5 V) 位宽 = 1 / 波特率 量出的位宽与理论值差得越多,误差越大
图 06 · 排查顺序:先波形、后代码。波形正常,问题多半在协议解析;波形异常,问题在硬件或配置。
05 / 运动解算

把“我想去哪”翻译成“每个关节转多少”

运动学就是位置和角度之间的换算。正解是“已知角度求末端位置”,逆解是“已知目标位置求角度”。逆解通常更难,因为可能有多解,也可能无解。

平面二连杆:正解与逆解

两根连杆、两个旋转关节,是最容易讲清楚的机械臂模型。设第一段长 l₁、第二段长 l₂,关节角为 θ₁、θ₂(相对角),末端坐标就是两个向量相加:

x = l₁ cos θ₁ + l₂ cos(θ₁ + θ₂)
y = l₁ sin θ₁ + l₂ sin(θ₁ + θ₂)
这就是正解:角度 → 位置。写代码时非常直接,几行三角函数就能算出来。

反过来,已知目标点 (x, y) 求角度就是逆解。先用余弦定理求肘关节角:

cos θ₂ = ( x² + y² − l₁² − l₂² ) / ( 2 l₁ l₂ )
再求肩关节角。这里要用 atan2(y, x) 而不是 atan(y/x),因为它能正确区分四个象限,也能处理 x = 0 的情况。
θ₁ = atan2(y, x) − atan2( l₂ sin θ₂ , l₁ + l₂ cos θ₂ )
θ₂ = ± arccos( … )
式中的 ± 就是两组解:肘向上、肘向下,机械结构决定了实际该选哪一组。
概念
可达范围是一个圆环:外半径 l₁ + l₂,内半径 |l₁ − l₂|。目标点落在环外或环内的空洞里,就是无解。
踩坑
acos() 的参数必须限制在 −1 ~ 1,否则会返回 NaN;角度与弧度别混用(180° = π rad);atan2 的参数顺序是 (y, x),写反了角度会整体转 90°。

交互演示 · 二连杆逆解

拖动目标点试试
θ₁(肩关节)—
θ₂(肘关节)—
目标点 (x, y)—
目标可达性—
连杆与关节 目标点 可达范围边界

差速底盘:速度怎么分配给两个轮子

两轮差速小车只有线速度 v 和角速度 ω 两个控制输入,但电机只接收左右轮速。中间的换算就是最简单的一次运动解算:

vL = v − ω L / 2
    vR = v + ω L / 2
L 为左右轮中心距。反过来,从编码器测到轮速,也能算出车体实际在怎么运动:
v = ( vR + vL ) / 2     ω = ( vR − vL ) / L
R = v / ω   (转弯半径)
ω = 0 走直线;v = 0 原地旋转;两者都不为 0 就是一段圆弧。让 v 与 ω 随时间变化,就能拼出任意路径。
踩坑
里程计不准,先标定 轮径 与 轮距:让车直行 2 m 看记录距离,原地转 10 圈看记录角度。轮距填错,转弯角度就会一直偏。

交互演示 · 差速底盘轨迹

调整 v 与 ω 看轨迹
左轮 vL—
右轮 vR—
转弯半径 R—
运动形式—
06 / 闭环控制

闭环 = 看见误差,然后修正它

开环控制是“我猜要给多少就给多少”,闭环控制是“我先看结果偏了多少,再决定给多少”。PID 是最常用的修正方式,三项分别对应现在、过去和趋势。

开环(无反馈) 输入 控制器 电机 / 机构 输出 负载变了、电池电压降了,输出就跟着变,没人纠正。 闭环(有反馈) + − PID 被控对象 输出 y 传感器 测量值回传,与目标比较得到误差 e
图 07 · 开环与闭环的区别:闭环把测量值引回来,让控制器持续“纠偏”。

PID 三项到底在做什么

u(t) = Kp e(t) + Ki ∫₀ᵗ e(τ) dτ + Kd de(t)/dt
其中 e = r − y,是目标值与测量值之差。
  • P 比例 · 看现在:误差越大输出越大。Kp 太小响应慢,太大就会冲过头、来回振荡。
  • I 积分 · 记住过去:把历史误差累加起来,专门消除“总是差一点”的稳态误差。
  • D 微分 · 预测趋势:误差变化越快,“刹车”越猛,能压住超调,但也会放大测量噪声。
离散化
单片机里不可能真的做连续积分,都按固定周期 T 近似:u[k] = Kp·e[k] + Ki·T·Σe[i] + Kd·(e[k]−e[k−1])/T。所以采样周期必须稳定,否则参数就失去意义。

交互演示 · 调一调 PID 看阶跃响应

被控对象含恒定负载扰动:J θ̈ + b θ̇ + τ_load = u
上升时间 10→90%
—
超调量
—
稳态误差
—
峰值
—
系统输出 θ(t) 目标值 1.0 rad ±5% 误差带
pid.c · 带积分限幅的位置式 PID
typedef struct {
  float kp, ki, kd, T;
  float i;                    // 积分累加项
  float prev_e;
  float out_min, out_max;
} pid_t;

float pid_step(pid_t *p, float target, float meas) {
  float e = target - meas;

  p->i += p->ki * e * p->T;                    // 积分项
  if (p->i > p->out_max) p->i = p->out_max;    // 积分限幅,防止饱和
  if (p->i < p->out_min) p->i = p->out_min;

  float d = (e - p->prev_e) / p->T;         // 微分(可加低通滤波)
  p->prev_e = e;

  float u = p->kp * e + p->i + p->kd * d;
  if (u > p->out_max) u = p->out_max;
  if (u < p->out_min) u = p->out_min;
  return u;
}

调参顺序与常见问题

  • 先固定采样周期:控制周期抖动比参数不准更致命,用定时器中断触发采样与计算。
  • 先把 Kp 加上去:从小往大调,直到响应变快但还没出现明显振荡。
  • 再加 Ki 消稳态误差:看输出是否慢慢爬到目标值;Ki 太大会出现低频摆动。
  • 最后加 Kd 压超调:有编码器噪声时,微分项要先低通滤波,否则电机会“滋滋”抖。
  • 回头微调:加完 D 之后响应会变慢,通常可以再稍微加一点 Kp。
踩坑
积分饱和:上电瞬间误差很大,积分项一路累加到极限,等接近目标时反而刹不住,出现大超调。解决办法是积分限幅,或者误差较大时暂停积分。
踩坑
微分放大噪声:直接对编码器差分,噪声会被放大成电机抖动。可以对 e 做一阶低通,或者用“测量值微分”代替“误差微分”。
理论
二阶系统的超调量只与阻尼比 ζ 有关:Mp = e−πζ/√(1−ζ²),上升时间约 1.8 / ωn。采样频率一般取闭环带宽的 10 倍以上。
07 / 项目记录

课余时间做的几个小东西

都是学生阶段的练习项目,目的不是做成产品,而是把上面这些知识串起来用一遍。下面记录的是我在做的过程中学到的部分。

● 在做 · 调速度环

两轮自平衡小车

用陀螺仪和加速度计做姿态解算,直立环与速度环串起来控制。目前能稳定直立,速度环还在调。

学到:传感器零漂要标定,机械中值要找准确;控制周期抖动比参数不精确更影响稳定性。
  • I²C 传感器
  • 互补滤波
  • 定时器 PWM
  • 串级 PID
● 在做 · 弯道会偏

差速循迹小车

灰度传感器阵列识别黑线位置,用 PID 修正左右轮速差。直道跑得比较稳,弯道还在调参数。

学到:差速解算的轮距参数必须标定,否则转弯角度一直有偏差;传感器高度与间距对识别效果影响很大。
  • 差速解算
  • 位置式 PID
  • ADC 采样
  • 电机驱动
● 已完成 · 点到点

二自由度机械臂

上面那个逆解演示就是按它的尺寸做的模型。目前能按给定坐标做点到点运动,接下来想试试轨迹插值。

学到:舵机角度要标中值;两组逆解要根据机械限位来选,不然会撞机架。
  • 二连杆逆解
  • 舵机 PWM
  • 角度标定
  • 轨迹插值
08 / 工具与资料

我常用的工具和学习资料

工具不在多,顺手就行。资料方面我的体会是:芯片手册永远比教程可靠,遇到时序问题直接翻参考手册,比搜半天博客快得多。

工具链

  • KiCad / 立创EDA
  • STM32CubeMX
  • Keil / PlatformIO
  • VS Code
  • 逻辑分析仪
  • 示波器
  • 万用表 / 可调电源
  • Python + NumPy
  • Git
  • 串口助手

调通信我用得最多的是逻辑分析仪:协议解码一开,起始位、地址、ACK 全在波形上,比猜快多了。

资料清单

  • 芯片数据手册 / 参考手册:外设寄存器、时序参数、电气特性的权威来源。
  • 《模拟电子技术基础》:先把器件特性弄明白,画板和选参数心里才有底。
  • 《自动控制原理》:讲清传递函数、稳定性与 PID 的来源,调参时不再全靠试。
  • 《机器人学导论》:运动学与坐标变换的入门教材,正解逆解讲得很系统。
  • 开源项目与公开原理图:看别人怎么布局、怎么加保护,收获常常比看教程大。
方法
先在仿真里验证思路,再上实物。控制参数先在电脑里跑出曲线,心里有数之后再烧进单片机,能省下很多拆板子的时间。
方法
每做完一个模块写一页笔记:参数怎么定的、现象是什么、问题出在哪。过两个月再看,这页笔记比记忆有用得多。
09 / 关于本站

关于这个学习笔记站点

这里是什么

我是一名在校学生,业余在学嵌入式相关的东西:电路、PCB、单片机、控制算法。这个站点是我自己整理的学习笔记,把平时看资料、做实验、踩坑的过程记下来,顺便画成图方便以后回顾。

它不是教程,更不是商业推广。内容都按我自己的理解写,可能有不准确甚至错误的地方,如果你发现问题,非常欢迎指出,我会去核对后修改。

声明
本站为个人非商业学习笔记,无广告、无付费内容,不承接业务。文中提及的芯片与工具仅作为学习记录,与厂商无关。

交流与联系

如果你也在学类似的东西,欢迎一起交流:

内容范围PCB · 单片机 · 运动解算 · 控制
更新方式学完一个模块更新一次

除特别注明外,本站笔记采用「署名—非商业性使用—相同方式共享」的方式共享,转载请注明出处。