在嵌入式领域,我们经常面临一个资源不对等的矛盾:Flash 空间很大(通常几百 KB),但 CPU 算力很弱(几十 MHz)。
如果你在单片机里写了 y = sin(x) 或者 temp = log(r), 编译器会生成几百行汇编代码来做泰勒级数展开。你的主循环周期瞬间就会被拉长。
查表法 (Look-Up Table, LUT) 就是解决这个问题的终极武器。
核心思想:“与其现场算,不如提前抄答案。” 利用廉价的存储空间(Flash)来存储预先计算好的结果,换取 CPU 的极速响应。
1. 场景一:三角函数与波形生成
假设你要做一个 SPWM 逆变器或者步进电机 S 型加减速,需要频繁计算 sin(x)。
笨办法:实时计算
#include <math.h> // 引入了庞大的数学库
float val = sin(angle * 3.14 / 180.0); // 浮点运算,耗时几十微秒,且需要 FPU 支持
查表法: 我们把 0~90 度的正弦值放大 1024 倍(定点化,避免浮点运算),提前算好存入数组。
// 放在 Flash (.rodata) 中,不占 RAM
const int16_t SinTable[91] = {
0, 17, 35, 53, ... 1024 // 对应 sin(0) ~ sin(90) * 1024
};
int16_t Fast_Sin(uint8_t angle) {
// 利用对称性,只需存 0-90 度,涵盖 0-360 度
if (angle <= 90) return SinTable[angle];
if (angle <= 180) return SinTable[180 - angle];
if (angle <= 270) return -SinTable[angle - 180];
return -SinTable[360 - angle];
}
收益:运算时间从 ~50us 降到 ~0.1us (几次内存读取)。
2. 场景二:NTC 热敏传感器 (非线性映射)
NTC 电阻随温度变化的曲线是非线性的,对应的 Steinhart-Hart 方程包含自然对数 ln,单片机算起来非常吃力。
查表策略: ADC 读到的电压值(0~4095)与温度是一一对应的。我们可以生成一张 “ADC值 -> 温度” 的映射表。
问题:如果做一个 int TempTable[4096] 的全表,太占空间了(4096 * 2 = 8KB)。如果 Flash 只有 32KB,这就很奢侈。
优化策略:分段查表 + 线性插值 (Linear Interpolation)
我们不需要存每一个 ADC 值,我们每隔 64 个点存一个温度数据。
// 假设 ADC 步长为 64
// Table[0] 对应 ADC=0 的温度
// Table[1] 对应 ADC=64 的温度...
const int16_t NTC_Table[] = { -40, -10, 25, 60, ... };
int16_t Get_Temp(uint16_t adc_val) {
uint8_t index = adc_val / 64; // 算出在哪两个点之间 (整数部分)
uint8_t remainder = adc_val % 64; // 算出偏移量 (小数部分)
int16_t y0 = NTC_Table[index]; // 左边的温度
int16_t y1 = NTC_Table[index + 1]; // 右边的温度
// 核心算法:线性插值 (y = y0 + slope * dx)
// 假设两点之间是直线,算出中间值
return y0 + (y1 - y0) * remainder / 64;
}
收益:
-
空间:表的大小缩小了 64 倍(仅需 100 多字节)。
-
精度:虽然略有误差,但对于测温来说通常足够(误差 < 0.5度)。
-
速度:依然只有简单的加减乘除,没有
log。
3. 场景三:状态压缩与位操作
查表法不仅能查数值,还能查“逻辑”。
案例:流水灯花样。 你需要控制 8 个 LED 亮灭,有很多种花样模式。 与其写一堆 if (mode == 1) LED = 0x55;,不如直接查表:
const uint8_t LED_Patterns[] = {
0x01, 0x02, 0x04, 0x08, // 流水左移
0x10, 0x20, 0x40, 0x80,
0x81, 0x42, 0x24, 0x18, // 两边向中间聚合
0xFF, 0x00, 0xFF, 0x00 // 闪烁
};
void LED_Task() {
static int i = 0;
PORTA = LED_Patterns[i]; // 直接输出,毫无逻辑负担
i = (i + 1) % sizeof(LED_Patterns);
}
这其实就是一种极简的微指令 (Microcode) 思想。
4. 查表法的工程原则
-
数据必须
const:一定要加const关键字,确保数组存放在 Flash (.rodata) 中。如果不加,启动代码会把它们搬运到 RAM (.data),瞬间撑爆你本来就不富裕的 RAM。 -
越界保护:查表意味着用输入作为索引。如果输入是外部传来的(比如串口数据),必须先检查是否超过了数组下标,否则会读取到随机乱码甚至引发 HardFault。
-
生成工具:对于大的表格(如 CRC 表、正弦表),不要手写。用 Python 脚本或 Excel 生成 CSV,然后复制进 C 代码中。
本章关键知识点
-
本质:用预处理(编译时计算)代替运行时计算。
-
权衡:如果计算太复杂(三角、对数、除法),或者逻辑太繁琐(花样控制),请优先考虑查表。
-
插值:是平衡表格大小和精度的关键技巧。
/***************************************************
* 本文为作者《嵌入式开发基础与工程实践》系列文章之一。
* 关注即可订阅后续内容更新,翻阅往期信息,采用异步推送机制,无需主动轮询。
* 转发本文可视为一次网络广播,有助于更多节点接收该信息。
***************************************************/
3645

被折叠的 条评论
为什么被折叠?



