ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

51单片机定点FFT频谱分析实战:Q15实现与LCD实时显示

51单片机定点FFT频谱分析实战:Q15实现与LCD实时显示 简介本资源是一套面向嵌入式初学者与单片机开发者的FFT频谱分析实践方案聚焦在资源受限的单片机平台上实现快速傅里叶变换并驱动液晶屏实时显示频谱结果。适用于电子类课程设计、智能仪器信号处理模块开发及电赛备赛等场景帮助学习者掌握从ADC采样、蝶形运算优化、复数内存管理到LCD波形可视化的完整链路。压缩包共15个文件含核心源码fft.c、启动文件STARTUP.A51、Keil工程配置uv2、opt、m51、编译中间产物lst、obj、hex及液晶显示相关配置fftye.Opt、fftye.Uv264KB体积精炼实用便于快速导入调试。已有265人下载学习资源结构清晰、工程可直接编译运行附带典型参数配置与频谱显示逻辑特别适合理解单片机FFT实时性约束下的算法落地细节与常见误差来源。1. 单片机上跑通 FFT 频谱分析不是调库而是真动手算出来的你手头那块 STC89C52 或 STC12C5A60S2没接 FPGA、没用 DSP 芯片、甚至没开浮点单元——但它真能实时画出正弦波的频谱峰这个fft.rar里的工程给出了答案它用纯 C 实现了 128 点基-2 DIT-FFT在 11.0592MHz 晶振下从 ADC 采样到 LCD 显示完整频谱仅耗时约 38ms含 128 点采样FFT幅值计算LCD 刷新。这不是 Keil 自带的 math.h 里藏的黑盒函数而是逐行可验的蝶形运算、位反转索引表、定点 Q15 幅值缩放。它解决的不是“能不能显示频谱”的问题而是“在 51 类单片机 256B RAM、无硬件乘法器、中断响应延迟不可控的硬约束下如何让 FFT 不崩、不溢、不丢点、不糊屏”。适合正在做噪声监测仪、电机振动初筛、音频简易分析模块的嵌入式工程师也适合想撕开 FFT 表面、看清内存布局与时间换空间本质的学生——因为所有.c、.a51、.hex全在里面连STARTUP.A51的堆栈初始化都暴露着。2. 基于 51 架构的定点 FFT 实现原理与关键参数设计2.1 为什么必须用定点 Q15 而非 float51 单片机尤其 STC 系列无硬件浮点单元Keil C51 编译 float 运算需调用庞大库函数单次乘加耗时超 200μs128 点 FFT 的 7 级蝶形共需 448 次复数乘加纯 float 实现将突破 90ms远超常见 20ms 采样窗口。本工程采用 Q15 定点格式1 位符号 15 位小数所有输入 ADC 值0–255经归一化映射为[-1, 1)区间整数// adc_val 为 0–255 的 uint8_t 值 int16_t x_q15 (int16_t)((adc_val - 128) 7); // 左移 7 位 → Q15 格式等效 ×0.0078125提示Q15 下乘法结果为 Q30需右移 15 位得 Q15但蝶形运算中两次乘法叠加易溢出故实际在fft.c第 89 行采用14保留额外 1 位保护位这是抗饱和的关键设计。2.2 蝶形运算的内存布局与索引优化基-2 DIT-FFT 要求输入序列按比特逆序排列。若每次计算都动态位反转51 的RL A指令需 7 次循环128 点开销过大。本工程在fft.c中预置bitrev_table[128]数组const uint8_t bitrev_table[128] { 0,64,32,96,16,80,48,112,8,72,40,104,24,88,56,120, 4,68,36,100,20,84,52,116,12,76,44,108,28,92,60,124, // ... 后续 96 个值完整表见源码第 32 行 };实际数据重排通过查表完成for(i0; i128; i) { temp x_real[i]; x_real[i] x_real[bitrev_table[i]]; // 原地置换 x_real[bitrev_table[i]] temp; }注意该表仅适用于 128 点若需 256 点须生成新表可用 Python 快速生成[int(format(i,08b)[::-1],2) for i in range(256)]且x_real[]数组长度必须严格匹配否则 LCD 显示频谱会错位。2.3 蝶形核心代码与缩放因子控制fft.c中butterfly()函数实现单级蝶形关键在于旋转因子Wn的定点化。本工程未用查表法节省 ROM而用#define Wn_R(k) (int16_t)(cos(2*PI*k/128)*32767)预计算 64 个余弦值存入cos_table[64]见fft.c第 45 行。蝶形计算如下// L: 当前级跨度如第1级L1第2级L2...第7级L64 // u: 旋转因子索引u j * 64 / L j为当前蝶形组内序号 int16_t wr cos_table[u]; // Q15 实部 int16_t wi sin_table[u]; // Q15 虚部sin_table 同理预置 // 复数乘法(ajb)*(cjd) (ac-bd) j(adbc) int32_t tr ((int32_t)x_real[k] * wr - (int32_t)x_imag[k] * wi) 14; int32_t ti ((int32_t)x_real[k] * wi (int32_t)x_imag[k] * wr) 14; x_real[k] x_real[j] - (int16_t)tr; // 蝶形输出 x_imag[k] x_imag[j] - (int16_t)ti; x_real[j] x_real[j] (int16_t)tr; x_imag[j] x_imag[j] (int16_t)ti;逻辑说明14是核心缩放——因wr/wi为 Q15x_real[k]也是 Q15乘积为 Q30右移 14 位得 Q16留 1 位防溢再赋值给 Q15 变量时自动截断低位保证幅值精度损失可控。若改为15高频分量会严重衰减若不移位则int16_t存储必然溢出。3. 从 ADC 采样到 LCD 显示的端到端流程与硬件协同3.1 ADC 采样时序与奈奎斯特边界控制本工程使用单片机内置 ADC如 STC12C5A60S2 的 10 位 ADC或外部 TLC5498 位串行 ADC。关键约束是采样率必须严格满足fs ≥ 2×fmax且fs必须使 128 点采集时间 ≤ FFT 计算时间。源码中adc.c通过定时器 T0 产生精确周期中断// Keil C51 初始化 T0 为方式 116 位定时设 TH0/TL0 使溢出周期 100μs TMOD | 0x01; // T0 方式 1 TH0 0xDC; // 100μs 11.0592MHz计算65536 - 11059200/12/10000 0xDC TL0 0x00; ET0 1; // 开 T0 中断 TR0 1; // 启动 T0每进入一次 T0 中断执行一次 ADC 转换并存入adc_buf[128]。128 点采集耗时128×100μs 12.8ms为后续 FFT 留足余量。若需分析最高 2kHz 信号则fs10kHz100μs 间隔已满足奈奎斯特准则若误设为 200μs 间隔fs5kHz则 2.5kHz 以上频率将发生混叠LCD 上出现虚假频谱峰。3.2 LCD 显示驱动与频谱可视化策略fft液晶中的 LCD 为 128×64 点阵型如 ST7920 控制器驱动代码位于lcd.c。频谱显示非简单打印数值而是绘制幅度柱状图X 轴0–63128 点 FFT 的前 64 点因实信号频谱对称Y 轴柱高 20×log10(|X[k]|)归一化后映射到 0–40 像素LCD 高度 64留 20 像素作坐标轴核心绘图逻辑for(k0; k64; k) { mag (int16_t)sqrt((long)x_real[k]*x_real[k] (long)x_imag[k]*x_imag[k]); // Q15 幅值 db (int8_t)(20 * log10((double)mag/32767.0) 80); // 80 使最小值≥0 height (db 40) ? 40 : db; // 限幅 lcd_draw_bar(k, 63-height, 1, height); // 在第k列画高度为height的竖条 }注意sqrt()使用sqrt_lut[256]查表法fft.c第 120 行因 51 无硬件开方查表耗时仅 3μslog10则用db_table[256]预存 0–255 对应 dB 值避免浮点运算。3.3 完整主循环与资源调度main.c中主循环结构体现嵌入式实时性设计void main() { init_system(); // 初始化 IO、ADC、LCD、定时器 while(1) { if(adc_done_flag) { // T0 中断置位 adc_done_flag 0; fft_128_point(); // 执行 FFT约 25ms calc_spectrum_mag(); // 计算 64 点幅值2ms lcd_refresh(); // 刷新 LCD1ms delay_ms(1); // 强制空闲 1ms确保下轮采样准时 } } }提示delay_ms(1)不可省略若删除lcd_refresh()后立即进入下次循环可能在 T0 中断到来前反复检查adc_done_flag导致 CPU 占用率 100%影响其他任务如按键扫描。实测中该延时使系统功耗降低 18%。4. 关键参数调试与典型故障排查表4.1 频谱失真与混叠的定位方法当 LCD 显示频谱峰位置错误如 1kHz 输入信号峰出现在 3kHz或出现多余杂峰按以下顺序排查现象可能原因验证方法修复操作频谱镜像不对称正负频段幅值差异大ADC 输入未偏置到 Vcc/2导致直流分量过大用示波器测 ADC 输入引脚确认静态电平 ≈ 2.5V5V 系统在信号输入端加隔直电容1μF 偏置电阻分压网络高频分量整体衰减14缩放过度或cos_table值精度不足将fft.c第 89 行14改为13观察高频峰是否增强检查cos_table是否用round(cos(x)*32767)生成而非floor()频谱峰宽过宽、分辨率低采样点数不足或窗函数缺失用已知 1.05kHz 正弦波输入观察峰是否跨 3 个以上频点在adc_buf[]采样后添加汉宁窗adc_buf[i] (int16_t)(adc_buf[i] * (0.5 - 0.5*cos(2*PI*i/128)));4.2 LCD 显示异常的硬件级诊断fftye.hex烧录后 LCD 全白/全黑/乱码优先检查三类信号V0 对比度电压ST7920 的第 3 脚V0需外接 10kΩ 电位器中心抽头接 V0两端接 Vcc 和 GND。实测若 V0 0.8V显示正常若 V0 0.2V字符淡不可见若 V0 1.5V屏幕全黑。PSB 引脚电平ST7920 的 PSB 脚决定串口/并口模式。本工程用串口4 线PSB 必须接 GND若误接 Vcc则lcd.c中SPI_Write()发送指令无效。复位时序ST7920 要求上电后等待 15ms 再发初始化指令。lcd_init()函数首行delay_ms(20)不可注释否则 LCD 控制器未就绪。4.3 内存溢出导致程序跑飞的快速捕获技巧51 单片机 RAM 仅 256Bx_real[128]256Bx_imag[128]256B已超限。本工程将x_imag[]定义为idata段内部 RAMx_real[]放xdata段外部 RAM但若未启用外部 RAM 或xdata地址冲突会导致指针越界。快速验证法// 在 main() 开头插入 unsigned char *p (unsigned char*)0x00; // 指向内部 RAM 起始 while(*p 0) p; // 扫描非零字节 if(p (unsigned char*)0x80) { // 若在 0x00–0x7F 发现非零说明栈溢出覆盖了内部 RAM lcd_print(RAM OVERFLOW!); // 触发告警 }实操建议在 Keil μVision 中打开Project → Options → Target勾选Use Memory Layout from Target Dialog手动设置IRAM为0x00–0x7FXRAM为0x0000–0xFFFF编译时查看fftye.M51文件末尾的MEMORY MAP确认x_real确实分配在XDATA区。5. 基于该工程的频谱分析能力扩展与精度强化技巧5.1 从单帧到连续频谱的滚动显示实现原始工程每帧刷新整个 LCD存在闪烁。升级为滚动显示需维护环形缓冲区#define SPEC_BUF_SIZE 8 // 存储最近 8 帧频谱 int16_t spec_buffer[SPEC_BUF_SIZE][64]; // [帧索引][频点] uint8_t spec_head 0; // 在 lcd_refresh() 中替换原绘图逻辑 for(k0; k64; k) { for(j0; jSPEC_BUF_SIZE; j) { uint8_t idx (spec_head - j SPEC_BUF_SIZE) % SPEC_BUF_SIZE; int16_t mag spec_buffer[idx][k]; // 绘制第 j 行对应频点 k 的像素y 50 - j } } spec_head (spec_head 1) % SPEC_BUF_SIZE; // 移动头指针效果LCD 显示 8 行频谱自下而上滚动直观呈现频率成分随时间变化适用于电机启停瞬态分析。5.2 信噪比提升滑动平均滤波与峰值保持对工业现场采集的振动信号原始频谱噪声明显。在calc_spectrum_mag()后插入两级处理// 1. 滑动平均3 帧抑制随机噪声 static int16_t mag_avg[64] {0}; for(k0; k64; k) { mag_avg[k] (mag_avg[k] * 2 mag[k]) / 3; // IIR 低通 } // 2. 峰值保持记录 10 帧内各频点最大值 static int16_t mag_peak[64] {0}; for(k0; k64; k) { if(mag_avg[k] mag_peak[k]) mag_peak[k] mag_avg[k]; else if(--peak_decay_cnt[k] 0) { // 每 5 帧衰减 1 单位 mag_peak[k] (mag_peak[k] * 9) / 10; peak_decay_cnt[k] 5; } } // 最终显示 mag_peak[k]此组合使 50Hz 工频干扰峰信噪比提升 12dB实测有效识别轴承故障特征频率如 162Hz 内圈缺陷。5.3 频率刻度标定与实际频率反推公式LCD X 轴 0–63 对应频率0 – fs/2但fs由定时器精度决定。精确标定需测量实际采样周期// 用示波器测 T0 中断引脚P3.4波形周期 T_actual // 则 fs 1 / T_actual, 第 k 点对应频率 f_k k × fs / 128 // 例如T_actual 100.2μs → fs 9980Hz → f_10 10×9980/128 ≈ 779Hz在lcd_print()中动态显示char buf[16]; sprintf(buf, f%dHz, (int)(k * 9980L / 128)); // k 为当前频点索引 lcd_print_xy(0, 0, buf);关键勿用晶振标称值11.0592MHz直接计算因陶瓷谐振器温漂可达 ±0.5%实测偏差常超 50Hz。本文还有配套的精品资源点击获取
返回列表