原理图如下:
代码分为两部分,一个C51一个汇编的~
- #include "REG51.H"
- #include "intrins.h"
- //--------------------常量定义-------------------------
- //ORDER是FFT所需要进行的阶段数目
- //N16 = 2^ORDER,这两个量是相互对应的
- #define ORDER 4
- #define N16 16
- //为了以整型变量实现小数计算,将小数乘上(2^SCALE),算好后再除回去
- #define SCALE 4
- //输入衰减
- #define IN_SCALE_DOWN 2
- //峰值点(帽子)停留的时间
- #define HAT_STAY_tiME 4
- //计时器T1的初值,以控制采样率
- #define SAMP_TIMER 0xfE//FE
- //0xCE 50us, i.e. sample rate = 20000 HZ
- //0x9C 100us, i.e. sample rate = 10000 HZ
- //0x38 200us, i.e. sample rate = 5000 HZ
- //就算时钟设得很短,采样率也只能达到 6000Hz. one 750Hz sound wave can be put in 8 sample points. 750x8=6000
- //AD模块需要的控制端定义
- sbit AD_OUT = P1^5;
- sbit AD_CLK = P1^7;
- sbit AD_CS = P1^6;
- //切换示波器与频谱显示的开关, 接在8位逻辑电平输出中的其中一个
- sbit SWITCH1= P3^3;
- //声明复数结构类型
- typedef struct
- {
- char real;
- char imag;
- }complex_t;
- //声明双字节的复数结构类型,用于中间运算过程
- typedef struct
- {
- int real;
- int imag;
- }complex_long_t;
- //--------------------查表-------------------------
- //FFT需要将数据下标按位倒序重排,此表列出了旧下标与新下标的对应关系
- char code reverse_table_16[16]={0,8,4,12,2,10,6,14,1,9,5,13,3,11,7,15};
- //复旋转因子表,依赖于SCALE==4,并且由下面两个式子给出
- //twiddle_table[i].real=(char)( cos(2*PI*i/N16) * (1<<SCALE) );
- //twiddle_table[i].imag=(char)( sin(2*PI*i/N16) * (1<<SCALE) );
- complex_t code twiddle_table[N16/2]={
- 16,0, 14,6, 11,11, 6,14, 0,16, -6,14, -11,11, -14,6,
- };
- //FFT向DCT转化所需要的系数,16个计算结果分别乘上这些系数。它们由下面式子给出
- //ww = exp(-1i*(0:n-1) * pi/(2*n)) / sqrt(2*n) , n=16
- //ww(0) = ww(0) / sqrt(2);
- complex_t code dct_coefficient[N16]={
- 4,0, 6,-1, 6,-1, 5,-2, 5,-2, 5,-3, 5,-3, 4,-4, 4,-4, 4,-4, 3,-5, 3,-5, 2,-5, 2,-5, 1,-6, 1,-6
- };
- //--------------------全局变量-------------------------
- //峰值点所处位置
- unsigned char xdata hatpos [N16];
- //峰值点停留时间(倒计时)
- unsigned char xdata hattime[N16] _at_ 0x0040;
- //峰值点的值
- unsigned char xdata hat [N16] _at_ 0x0030;
- //采样得到的原始数据
- char xdata src[N16] _at_ 0x0020;
- //计算得到的频谱数据
- char xdata am0[N16] _at_ 0x0010;
- //显示模块的数据暂存
- char data am [N16] _at_ 0x5F;
- //显示模块的“行状态”标记
- char data lstate;
- //零频补偿,将输入信号用软件的方法调整到平均值为零,因为硬件电路调整偏置到0以后,会有一定的漂移
- unsigned char data in_offset=0x07A;
- //--------------------函数-------------------------
- //汇编函数:显示中断服务子程序。由计时器T0控制,每次计满则调用一次
- extern void display_int(void);
- //实现两个复数的乘法。输入为单字节复数,输出为双字节复数
- complex_long_t fft_mult(complex_t a,complex_t b){
- complex_long_t c;
- c.real=(int)a.real * b.real - (int)a.imag * b.imag;
- c.imag=(int)a.real * b.imag + (int)a.imag * b.real;
- return c;
- }
- //实现两个复数的加法。并且假定b已经乘上了(2^SCALE),而a没有乘
- complex_t fft_add(complex_t a,complex_long_t b){
- complex_t c;
- c.real = (char)((((int)a.real << SCALE) + b.real) >> SCALE);
- c.imag = (char)((((int)a.imag << SCALE) + b.imag) >> SCALE);
- return c;
- }
- //原理同“复数的加法”
- complex_t fft_sub(complex_t a,complex_long_t b){
- complex_t c;
- c.real = (char)((((int)a.real << SCALE) - b.real) >> SCALE);
- c.imag = (char)((((int)a.imag << SCALE) - b.imag) >> SCALE);
- return c;
- }
- //中断初始化
- void ini_interrupt(){
- lstate=0x80; //显示屏“行状态”的初始化
- EA=1;
- IP=0;//
- TMOD=0;
- TCON=0;
-
- //INT0 Power off
- EX0=1;
- IT0=0;////////////////////低电平触发
-
- //Timer0 Display
- ET0=1;
- TMOD |= 0x01; //M1 M0 = 0 1
- TH0=0x00;
- TL0=0x00;
-
- //Timer1
- ET1=0; //查寻法
- TMOD |= 0x20; //M1 M0 = 1 0
- TH1=SAMP_TIMER;
- TL1=SAMP_TIMER;
- TCON=0x050; //TR0 TR1
- }
- //单个数据点的采集/////////////////////采到的是单数据点8位数据
- char AD_convert(void)
- {
- char i= 0;
- char temp=0;
- for(i=0;i<8;i++)
- {
- AD_CLK=1; //AD_CLK=1;
- AD_CLK=0; //AD_CLK=0;
- temp<<=1;
- temp|=AD_OUT;
- }
-
- AD_CS=1; //AD_CS=1;
- return temp;
- }
- //16个数据点的采集/////////////////////////////////////////////VOID函数,数据放进SRC
- void sample16()
- {
- char n,k;
- char sum=0
-
- TF1=0;
- lstate=0x080;//if runs to here, means there's a new set of am0[] to be display.
- TR0=0
- for (n=-1;n<N16;n++
- {
- while (TF1==0);
- TR1=0;
- TF1=0;
- k=( AD_convert()-in_offset );
- TR1=1;
- k>>=IN_SCALE_DOWN;
- sum+=k;
- if (k>7)
- k=7;
- else if(k<-7)
- k=-7;
-
- if(n>=0)
- {
- src[n]=k;
- }
- }
- if (sum>5)
- {
- in_offset++;
- }
- else if (sum<-5)
- {
- in_offset--;
- }
- P0=~in_offset;
- TR0=1;
- }
- //主程序
- void main(void)
- {
- //局部变量的含义将在调用时说明
- char n, k, m;
- char k2;
- char twiddle;
- char twiddle_jump;
- char twiddle_max;
- char n_jump;
- char n_start;
- complex_long_t tmp_product;
- complex_t twiddle_factor;
- complex_t dest[N16];
- char finaltmp;
-
- ini_interrupt();
- //主循环
- while(1)
- {
- sample16();
- for(n=0; n < N16; n++)
- {
-
- //同时进行两项“重新排序”,
- //src方括号内的式子实现奇偶分离,
- //dest方括号内的式子实现FFT的下标按位倒序
- if (n<(N16/2))
- {
- dest[reverse_table_16[n]].real = src[n*2];
- }
- else
- {
- dest[reverse_table_16[n]].real = src[(2*N16-1)-2*n];
- }
- dest[reverse_table_16[n]].imag = 0;
- }
- for(k=0; k < ORDER; k++)
- {
- n = 0;
- k2 = (1<<k);
- //计算旋转因子的单次旋转量,和最大旋转量
- twiddle_max = (1<<( ORDER - 1));
- twiddle_jump = (1<<( ORDER - k - 1)); //equals 2^(kmax - k)
- //蝴蝶翅膀的翼展
- n_jump = k2<<1;
- n_start = 0;
- for(twiddle = 0; twiddle < twiddle_max; twiddle += twiddle_jump)
- {
- //旋转因子,由对称性,只取 < N/2
- twiddle_factor.real = twiddle_table[twiddle].real;
- twiddle_factor.imag = -twiddle_table[twiddle].imag;
- for(n=n_start; n < N16; n+= n_jump)
- {
- m = n+k2;
- //单次蝶形运算
- tmp_product = fft_mult(dest[m], twiddle_factor);
- dest[m] = fft_sub(dest[n], tmp_product);
- dest[n] = fft_add(dest[n], tmp_product);
- }
- n_start++;
- }
- }
-
- ////////////////////////////////////////////进行计算结果的处理//////////////////////////////////////////////////////////////////////
- n=0;
- for (k=0;k<N16;k++)
- {
-
- //调整系数
- finaltmp = ( dest[k].real * dct_coefficient[k].real - dest[k].imag * dct_coefficient[k].imag ) >> SCALE;
- //取绝对值
- if (finaltmp<0)
- {
- finaltmp = -finaltmp;
- }
- if (finaltmp>8)
- {
- finaltmp=8;
- }
- if(finaltmp>0)
- {
- am0[k]=finaltmp;
- else
- {
- am0[k]=0;
- }
- }
-
- }
-
- return;
- }
复制代码- ;计时器0初值
- TH_INI EQU 0xFC;f4
- TL_INI EQU 0x06;06
- ;连线: P1.0、P1.1、P1.2分别与LED双 {MOD}点阵显示模块的SCLK 、RCLK、DIN相连
- LED_CLK BIT P1.3;
- LED_CLK2 BIT P1.4
- LED_RCLK BIT P1.2;
- LED_DIN BIT P1.0;
- LED_DIN2 BIT P1.1
- EXTRN DATA (lstate)
- EXTRN DATA (am)
- EXTRN XDATA (am0)
- ;EXTRN XDATA (hatpos)
- ;显示中断服务子程序
- CSEG AT 0BH ;连接器必须把函数入口放在0BH处
- LJMP _display_int
- ?PR?_display_int?MAIN SEGMENT CODE ;在程序存储区中定义段
- PUBLIC _display_int ;声明全局函数
- RSEG ?PR?_display_int?MAIN ;函数可被连接器放在任何地方。
-
- USING 2
- _display_int:
- PUSH ACC
- PUSH DPH
- PUSH DPL
- PUSH PSW
- USING 2
- MOV PSW,#10H
- CLR P2.0
- SETB P2.0
- MOV TH0,#TH_INI
- MOV TL0,#TL_INI
- ;CLR TF0
- ;刷新显示;;;;;;;;;;;;;;;;;;;;;;;;;;;;;左移一位,向上选通一行;;;;;;;;;;;;;;;;循环的
- MOV A,lstate
- RL A
- MOV lstate,A
- CJNE A,#1,skip_copy;;;;;;;;;不相等则跳转
- ;复制数据//////////////////////////////////////////////////////////////////////////am0复制到am中
- MOV DPTR,#am0
- MOV R1,#((am)+15)
- MOV R7,#16;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;R7=16
- copy_loop:
- MOVX A,@DPTR;
- MOV @R1,A;
- INC DPTR
- DEC R1
- DJNZ R7,copy_loop;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;R7用于计数
- ;选中需要输出的行
- MOV A,lstate
- MOV R7,#8 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;R7=8
- line_out1:
- CLR LED_CLK
- RRC A
- MOV LED_DIN,C
- CLR C
- NOP
- NOP;;;
- SETB LED_CLK
- NOP;;;
- NOP
- DJNZ R7,line_out1;;;;;;;;;;;;;;;;;;;;;;;;R7计数
-
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;行信息录入完毕;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- MOV R0,#LOW (am)
- mov r1,#low(am)
- MOV R7,#2;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;R7=2
- disp_loop7:
-
- ;绿 {MOD},用于显示频谱的瞬时值
- MOV R5,#8;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;R5=8
- disp_loop5:
- CJNE @R0,#0,a_not_0
- SETB LED_DIN2;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;LED_DIN2
- NOP
- NOP
- CLR LED_CLK2
- NOP
- NOP
- SETB LED_CLK2
- SJMP end_a_not_0
- a_not_0:
- CLR LED_DIN2
- NOP
- NOP
- CLR LED_CLK2
- NOP
- NOP
- SETB LED_CLK2
- NOP
- DEC @R0
- end_a_not_0:
- INC R0
- DJNZ R5,disp_loop5;;;;;;;;;;;;;;;;;;;;;;;;;R5计数
-
- DJNZ R7,disp_loop7;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;R7计数
- SETB LED_RCLK
- NOP
- CLR LED_RCLK
-
- POP PSW
- POP DPL
- POP DPH
- POP ACC
- RETI
- END
复制代码
因为基于别人的程序修改的,我感觉都看明白了。但是实际
仿真时发现,输入一个正弦波,16x16LED屏依旧在跳动,而不是某一频率上强度以稳定高度显示。
我想,采集16个数据进行FFT,在16个频段上显示这件事靠谱嘛?鉴于感觉很多例子都是这么做出来的,我想一定是程序里有问题,但是到底是哪里有问题呢TwT
请教各位大大了!
这个是原本的程序里就是这么设计的
in_offset是零频补偿,将输入信号用软件的方法调整到平均值为零,因为硬件电路调整偏置到0以后,会有一定的漂移
我就理解为一种大小的调节啦,毕竟16x16显示的范围有限。
一周热门 更多>