姿态与状态估计:互补滤波、Mahony、Madgwick、EKF、UKF、MSCKF、粒子滤波
姿态与状态估计:互补滤波、Mahony、Madgwick、EKF、UKF、MSCKF、粒子滤波 做机器人,尤其是四足、无人机这类要「知道自己在哪、姿态什么样」的机器,状态估计是绕不过去的一关。 IMU 很便宜,但它给的是角速度和加速度,不是姿态、更不是速度。你想知道机身现在朝哪、在往哪动、走多快,都得靠「估」。于是就有了一整排算法:互补滤波、Mahony、Madgwick、EKF、UKF、MSCKF、粒子滤波……名字一堆,经常分不清谁是谁、什么时候该用谁。 这篇文章把它们从头到尾捋一遍,讲清每个算法在解决什么问题、核心思想是什么、数学上怎么推出来的、彼此什么关系,最后落到四足机器人的实际场景。我自己在四足机器人上用 BMI088 做过 Mahony 姿态估计,互补滤波那部分会带上真实代码。 先理清:状态估计到底在估什么 以四足机器人为例,状态估计要回答三类问题: 姿态:机身当前的 roll / pitch / yaw,或者用四元数、旋转矩阵表示。 线速度 / 位置:机身往哪动、走多快、到哪了。 零偏:陀螺和加速度计都有零偏(bias),而且随温度漂移,得在线估出来,否则积分一下...
通信协议名词,都该归到 OSI 的哪一层
通信协议名词,都该归到 OSI 的哪一层 学通信最烦的就是名词多。UART、SPI、I2C、CAN、RS485、TCP、UDP、HTTP、MQTT……每一个都眼熟,但真让你说清楚「它是什么、在哪一层、跟旁边的什么关系」,经常就卡住了。 单片机的总线和计算机网络的协议,看起来是两个世界,其实能塞进同一套坐标系里:OSI 分层模型。这篇文章就把这些名词挨个归位,顺便说说它们是怎么串成一条链的。 先立一个坐标系 分层这件事,不用把七层背得滚瓜烂熟,记住一条主线就够了: 数据要传出去,得解决「怎么在线上表示 0 和 1」(物理层),「怎么把 0 和 1 组成有意义的帧、发给谁」(数据链路层),「怎么跨网络找到目的地」(网络层),「怎么保证传对、传稳」(传输层),「上面跑的具体业务」(应用层)。 而单片机里的那堆总线——UART、SPI、I2C、CAN、RS485——绝大多数只干到物理层 + 数据链路层就停了。计网里的 IP、TCP、HTTP 这些,才往网络层、传输层、应用层走。这就是为什么它们平时看起来像两个世界:一个在底层管「位」,一个在上层管「包」和「业务」。 下面分开看。 单片机侧...
把操作系统的进程、同步、死锁,放进 FreeRTOS 里看
把操作系统的进程、同步、死锁,放进 FreeRTOS 里看 操作系统课上的东西,很多都是干巴巴的文字:进程和线程的区别、PV 操作、死锁的四个必要条件。背的时候能背下来,但总觉得隔着一层——“到底什么时候会用到?” 直到我开始用 FreeRTOS 写四足机器人的固件,才发现这些东西根本不是课本里的抽象概念,而是每天都在代码里撞见的东西。这篇文章就把进程、同步互斥、死锁这三块,挨个搬到 FreeRTOS 里看一遍。 进程和线程,FreeRTOS 里只有一个 task 课本上最经典的一组概念:进程是资源分配的基本单位,有独立的地址空间;线程是调度的基本单位,共享所属进程的地址空间。 放到 FreeRTOS 里,这组概念会直接塌缩成一个东西:task。 原因很简单,FreeRTOS 跑在裸机 MCU 上,没有 MMU、没有虚拟内存,整个系统就一个地址空间。每个 task 有自己的栈和 TCB(Task Control Block),但所有 task 共享同一块全局内存。所以: 它像线程——独立执行流、独立栈、统一调度; 但它不是进程——没有地址空间隔离,一个 task 写坏内存,可能...
策略梯度定理推导
策略梯度定理推导 这篇东西是我自己推导策略梯度定理时顺手记下来的。网上关于这个定理的资料不少,但大多数要么跳步太厉害,要么在关键的地方语焉不详——比如那个"交叉项等于零"到底为什么等于零,比如 $P_\theta(s_t=s)$ 到底能不能提到梯度外面。所以我把每一步都写了下来,主要是给自己看的,顺便整理成一篇能读的东西。 前置设定 无限时域折扣 MDP,老规矩: 状态空间 $\mathcal{S}$,动作空间 $\mathcal{A}$ 环境状态转移核:$P(s’|s,a)$(环境动力学,与策略参数 $\theta$ 无关) 参数化策略:$\pi_\theta(a|s)$ 折扣因子:$\gamma\in[0,1)$ 轨迹:$\tau=(s_0,a_0,s_1,a_1,\dots)$ 单步奖励:$r(s_t,a_t)$ 回报(从时刻 $t$ 开始): $$ G_t = \sum_{k=t}^{\infty}\gamma^{k-t}r(s_k,a_k) $$ 这里顺便提一下两种常见的目标函数定义——后面会看到,选哪个会影响最终公式前面有没有那个 $\frac...
给机器人写了个安卓蓝牙遥控 App
从零写一个安卓蓝牙遥控 App——USB 串口、UVC 图传与自定义通信协议 前阵子为了控制一台机器人底盘,我需要一个手机遥控 App。市面上能搜到的蓝牙遥控器 App 要么功能过于简陋(就几个方向按钮),要么图传方案和自己用的不匹配。与其改别人的代码,不如自己写一个,反正需求也不算复杂: 通过 USB 串口(或者蓝牙 SPP)与机器人通信 通过 OTG 接入 UVC 视频采集卡显示 5.8G 模拟图传画面 支持摇杆、按键和矩阵操作,能切换多种工作模式 通信协议自己定,灵活扩展 这篇文章就记录一下这个 App 的完整设计与实现过程。 1. 整体架构 项目使用 Kotlin + Jetpack 全家桶,没有引入复杂的架构框架。核心模块就四个文件: app/src/main/java/com/ek/blectr/├── MainActivity.kt # 主界面 + 控制逻辑├── UsbSerialController.kt # USB 串口通信封装├── UvcPreviewController.kt # UVC 图传预览封装├── Joystick...
从 Bellman 到 PPO,RL 为什么这么设计
强化学习算法的设计美学——从 Bellman 到 PPO 的直觉路径 前面两篇我们聊了损失函数和优化器的设计哲学。但有一个领域的损失函数和优化器设计,比监督学习更"绕"——强化学习。 在监督学习中,你的标签是上帝给的,错了就是错了。但在强化学习中,你只有一个延迟的、嘈杂的、稀疏的标量奖励信号。更麻烦的是:你在用当前策略收集的数据来更新这个策略本身,更新完你又拿新策略去跑数据——你的训练数据一直在变。 这种"自己给自己出题,自己又去解题"的循环,使得 RL 的算法设计比监督学习多了一层曲折。这篇博客会沿着 RL 的算法演进路径,拆解每个关键设计背后的直觉——不是为了列公式,而是为了理解为什么一个问题被反复用不同方式解决。 1. 核心难题:状态价值和动作价值 先确立两个基本概念。 在 RL 中,智能体需要回答的根本问题是:在某个状态下,做哪个动作最好? 但"好"的定义是模糊的——好的动作会带来即时奖励,也可能带来长期回报。于是我们定义了两种价值函数: 状态价值 $V(s)$:从这个状态出发,按当前策略 $ \pi $,未...
SGD 到 Adam,优化器到底在干什么
优化器的直觉——从 SGD 到 Adam,每一步为什么这样走 上一篇文章我们聊了损失函数的设计哲学——一个函数告诉你"模型现在有多差"。但知道有多差只是第一步,更关键的问题是:知道了差在哪,该怎么走? 这就是优化器的工作。 从最早的梯度下降(SGD),到带动量(Momentum),到自适应学习率的 AdaGrad/RMSProp,再到集大成者 Adam——这条进化路径不是零散的拼凑,而是人们在实践中一步步回答同一个问题的过程: 用梯度更新参数时,怎么才能又快又稳地走到最优解? 这篇博客会沿着这个进化路径,讲清楚每个优化器解决了什么问题、又是怎么解决的,以及它们各自在什么场景下好用。 1. SGD:最朴素的走法 $$ \theta_{t+1} = \theta_t - \eta \cdot g_t $$ vanilla SGD 的逻辑最简单不过:看梯度往哪指,就往哪走一步,步长固定为 $\eta$。 好在哪里?直觉、简单、参数少。在凸优化理论上,SGD 有很好的收敛保证。 但实际训练中问题一堆: 步长难选。设大了震荡甚至发散,设小了龟速收敛。而且一个好的步长...
为什么回归用 MSE、分类用交叉熵
损失函数的设计哲学——从直觉理解为什么损失函数要长这样 如果你写过几个 PyTorch 训练脚本,大概已经对 nn.MSELoss 和 nn.CrossEntropyLoss 这两个名字烂熟于心。你可能也知道"回归用 MSE,分类用 CrossEntropy"这条经验法则,甚至背过交叉熵的公式: $$ \mathcal{L}{CE} = -\sum{c} y_c \log(p_c) $$ 但你有没有停下来想过——为什么? 为什么回归不直接用绝对误差(L1),非要用平方(L2)?为什么分类不用 MSE 做损失?为什么交叉熵里的那个 $\log$ 恰到好处,换成别的就不行? 这些问题背后不是拍脑袋的巧合。每一种损失函数的设计,都是对人类认知目标的一种数学建模。这篇博客想做的,就是把几个最常用的损失函数从设计动机到数学形式拆一遍——不是为了背公式,而是为了理解如果换你来设计,你也会这么想。 1. 先想清楚:损失函数到底在做什么 在说具体函数之前,先明确一个最基础的问题。 训练一个模型,本质是一个 优化问题: $$ \theta^* = \arg\min_\thet...
在 STM32H723 上手写一个 RTOS 内核
从零写一个 RTOS 内核——在 STM32H723 上 市面上有很多 RTOS:FreeRTOS、RT-Thread、μC/OS、Zephyr……它们都很好用,但用久了总有种隔靴搔痒的感觉——你知道怎么调 API,但不知道进去之后到底发生了什么。 这篇文章的目的很简单:在 STM32H723 上,从零写一个能跑起来的 RTOS 内核,包含任务创建、上下文切换、调度器启动、SysTick 时基、阻塞延时和空闲任务。 不依赖任何第三方 RTOS 源码,只靠 ARM Cortex-M7 的硬件特性和纯粹的 C + 内联汇编。写完以后,你将对 RTOS 的底层机制有一个肌肉记忆级别的理解。 全文代码均在 STM32H723 上实测通过,工具链为 ARM GCC。 1. 设计目标 在动手之前,想清楚我们要做什么、不做什么是很有必要的。 要做的: 多任务并发(伪并行,单核分时) 基于优先级的抢占式调度 SysTick 提供时基 阻塞延时(delay_ms 而不是 HAL_Delay) 空闲任务(没有任务执行时 CPU 进入 WFI) 不做的(后续可以单独写): 任务间同步(信号...
四足 RL 调参实录:PPO 那些让人抓狂的参数
PPO 在四足机器人控制中的应用——从理论到调参实战 如果你已经看过前面的 rsl_rl 源码解析,应该对 PPO 的工程实现有了清晰的了解。但那篇文章的重点是"代码怎么写",本文则回答另一个问题:PPO 在四足机器人 locomotion 中到底是怎么工作的、参数怎么调、坑在哪里? 如果你跑过四足 RL 训练,大概率遇到过这些场景: 训练两小时,loss 曲线看着很漂亮,结果机器人原地抽搐了一下就趴了 换了一组 reward 权重,PPO 就不收敛了,明明之前跑得好好的 试着把 clip range 从 0.2 调到 0.1,方差是小了,但怎么跑也跑不快 明明 GAE lambda 从 0.95 改到 0.99,步态反而更差了 这些都不是 bug,是 PPO 在连续控制问题——尤其是 locomotion——中的特有现象。本文从 locomotion 的视角重新理解 PPO 的每个组件,然后给出实战调参指南。 1. 重新理解 PPO——从 locomotion 的视角 PPO 的核心公式大家都很熟悉了: $$ L^{CLIP}(\theta) = \m...





