一、实验目的
本实验目的是利用实验箱构建一个信号采集和分析系统。
二、实验内容
三、实验步骤
1、键盘及LED的设计与制作 –编写单片机程序,控制按键,控制灯的亮灭, 测试所有能控制的按键和LED
2、液晶屏显示的设计与制作 –测试在液晶屏上显示波形和数据
3、以信号发生器输入单频正弦波,以16个 指示灯代表16个单频点,频点在200Hz ~ 10kHz任选,信号发生器每次产生一种频点 正弦波,AD采集,对应频点的指示灯会亮;
4、把指示灯显示频点改成用液晶显示
5、液晶屏同时显示频谱和时域波形
四、实验原理
1、AD采样模块
在这个模块中,每过一段时间,AD采样器采集一个数据存入数组中,并将存储的二进制数转换成对应的电压值,通过代码:collect0[t].real = ADC12MEM0*3.0 / 0x03ff;实现。采集满N个值之后,令标志变量flag=1,表示一组数据采集完毕,进入下一模块,即FFT变换。我们将信号发生器输出的模拟电压范围设定为0V~2V ,这是因为:若电压值太大,会导致结果波形失真;若出现负电压,则因为AD模块不支持负电压的采集,结果可能会出现错误。
2、FFT处理模块
该模块将采集进来的数据进行FFT变换,变换后的实部即为频谱,用于LCD的频谱绘制。绘制时,以各频点的幅频值与最大幅频值的相对大小进行绘制;变换后模值最大的频点对应的频率即为待测频率,用fre表示,便于屏幕及LED频率显示;根据求得的fre,可求得若干函数值,用于LCD的时域波形绘制。
3、 LCD液晶屏显示模块
该模块用于显示时域波形、频谱、学号、所测频率等内容。关键语句:
清屏:
etft_AreaSet(0,0,239,319,0);//清屏
显示:
sprintf(buffer, "Frequency: %5d Hz",fre);
etft_DisplayString(buffer, 0, 32, 0xFFFF, 0x0000);//显示频率
同时,在模块中加入了延时语句,使得波形可以保持一定的时间。时间到后,LCD会进行刷新,重新显示波形和数据。
4、LED灯显示模块
LED1作为采样指示灯,ADC模块每采一次样闪烁一次,由于采样频率极高,但中断调用间隔较长,实际显示为每次进入ADC采样中断LED1闪烁一下。
使用LED2、LED3、LED4、LED5显示频率,标号分别为5.7、4.7、4.6、4.5。受LED灯数目的,当用LED显示频率时(二进制表示),只能精确到1kHz。如当1500<=fre<2500时,用2kHz表示,灯显示为0010;当5500<=fre<6500时,用6kHz表示,灯显示为0110,其余类似。
通过以上模块,可以实现以下功能:AD模块采集N个数据后,FFT模块对数据进行处理,FFT变换后,实部为频谱,用于LCD的频谱绘制;模值最大的频点为所求频率,在LED和LCD上显示;再求得一些采样点的函数值,在LCD上绘制时域波形。最终,频率显示在LED灯和LCD上,波形和频谱显示在LCD上。图像保持一段时间后,LCD会进行刷新,重新显示波形和数据。
附录:
1、键盘及LED的设计与制作
#include #include #include #include "dr_i2c.h" #include "dr_tft.h" #define TEMP_ADDR 0x2A #define TEMP_LOCAL 0x00 #define TEMP_REMOTE 0x01 #define TEMP_CONFIG1 0x09 #define TEMP_CONFIG2 0x0A #define TEMP_NCORR 0x21 #define BATT_ADDR 0x55 #define BATT_VOLTAGE 0x04 #define BATT_CURRENT 0x10 #define BATT_SOC 0x1C #define BATT_CAPA 0x0C #define BATT_FLAG 0x06 int TEMP_LOCAL_INDEX; int TEMP_REMOTE_INDEX; int BATT_VOLTAGE_INDEX; int BATT_CURRENT_INDEX; int BATT_SOC_INDEX; int BATT_CAPA_INDEX; int BATT_FLAG_INDEX; typedef struct { const volatile uint8_t* PxIN; volatile uint8_t* PxOUT; volatile uint8_t* PxDIR; volatile uint8_t* PxREN; volatile uint8_t* PxSEL; } GPIO_TypeDef; const GPIO_TypeDef GPIO4 = { &P4IN, &P4OUT, &P4DIR, &P4REN, &P4SEL }; const GPIO_TypeDef GPIO5 = { &P5IN, &P5OUT, &P5DIR, &P5REN, &P5SEL }; const GPIO_TypeDef GPIO8 = { &P8IN, &P8OUT, &P8DIR, &P8REN, &P8SEL }; const GPIO_TypeDef* LED_GPIO[5] = {&GPIO4, &GPIO4, &GPIO4, &GPIO5, &GPIO8}; const uint8_t LED_PORT[5] = {BIT5, BIT6, BIT7, BIT7, BIT0}; void initClock() { while(BAKCTL & LOCKIO) // Unlock XT1 pins for operation BAKCTL &= ~(LOCKIO); UCSCTL6 &= ~XT1OFF; //启动XT1 P7SEL |= BIT2 + BIT3; //XT2引脚功能选择 UCSCTL6 &= ~XT2OFF; //启动XT2 while (SFRIFG1 & OFIFG) { //等待XT1、XT2与DCO稳定 UCSCTL7 &= ~(DCOFFG+XT1LFOFFG+XT2OFFG); SFRIFG1 &= ~OFIFG; } UCSCTL4 = SELA__XT1CLK + SELS__XT2CLK + SELM__XT2CLK; //避免DCO调整中跑飞 UCSCTL1 = DCORSEL_5; //6000kHz~23.7MHz UCSCTL2 = 20000000 / (4000000 / 16); //XT2频率较高,分频后作为基准可获得更高的精度 UCSCTL3 = SELREF__XT2CLK + FLLREFDIV__16; //XT2进行16分频后作为基准 while (SFRIFG1 & OFIFG) { //等待XT1、XT2与DCO稳定 UCSCTL7 &= ~(DCOFFG+XT1LFOFFG+XT2OFFG); SFRIFG1 &= ~OFIFG; } UCSCTL5 = DIVA__1 + DIVS__1 + DIVM__1; //设定几个CLK的分频 UCSCTL4 = SELA__XT1CLK + SELS__DCOCLK + SELM__DCOCLK; //设定几个CLK的时钟源 } int main( void ) { // Stop watchdog timer to prevent time out reset WDTCTL = WDTPW + WDTHOLD; initClock(); initI2C(); initTFT(); etft_AreaSet(0,0,319,239,0); int i; for(i=0;i<5;++i) *LED_GPIO[i]->PxDIR |= LED_PORT[i]; //设置各LED灯所在端口为输出方向 P4REN |= 0x1F; //使能按键端口上的上下拉电阻 P4OUT |= 0x1F; //上拉状态 uint8_t last_btn = 0x1F, cur_btn, temp; while(1) { int temp1, temp2; char buffer[41]; cur_btn = P4IN & 0x1F; temp = (cur_btn ^ last_btn) & last_btn; //找出刚向下跳变的按键 last_btn = cur_btn; int i; for(i=0;i<5;++i) { if(temp & (1 << i)) { *LED_GPIO[i]->PxOUT ^= LED_PORT[i]; //翻转对应的LED __delay_cycles(3276); //延时大约100ms temp1=7-i; temp2=5-i; sprintf(buffer, "Switch %d LED %d", temp1, temp2); etft_DisplayString(buffer, 0, 0, 0xFFFF, 0x0000); sprintf(buffer, "temp %d", temp); etft_DisplayString(buffer, 0, 16, 0xFFFF, 0x0000); } } __delay_cycles(2000000); } } 2、信号采集与显示 #include #include #include #include "math.h" #include "dr_tft.h" #define N 256 //FFT点数 #define PI 3.14159265327 volatile long IntDegF; volatile long IntDegC; void initClock() { while(BAKCTL & LOCKIO) // Unlock XT1 pins for operation BAKCTL &= ~(LOCKIO); UCSCTL6 &= ~XT1OFF; //启动XT1 P7SEL |= BIT2 + BIT3; //XT2引脚功能选择 UCSCTL6 &= ~XT2OFF; //启动XT2 while (SFRIFG1 & OFIFG) { //等待XT1、XT2与DCO稳定 UCSCTL7 &= ~(DCOFFG+XT1LFOFFG+XT2OFFG); SFRIFG1 &= ~OFIFG; } UCSCTL4 = SELA__XT1CLK + SELS__XT2CLK + SELM__XT2CLK; //避免DCO调整中跑飞 UCSCTL1 = DCORSEL_5; //6000kHz~23.7MHz UCSCTL2 = 20000000 / (4000000 / 16); //XT2频率较高,分频后作为基准可获得更高的精度 UCSCTL3 = SELREF__XT2CLK + FLLREFDIV__16; //XT2进行16分频后作为基准 while (SFRIFG1 & OFIFG) { //等待XT1、XT2与DCO稳定 UCSCTL7 &= ~(DCOFFG+XT1LFOFFG+XT2OFFG); SFRIFG1 &= ~OFIFG; } UCSCTL5 = DIVA__1 + DIVS__1 + DIVM__1; //设定几个CLK的分频 UCSCTL4 = SELA__XT1CLK + SELS__DCOCLK + SELM__DCOCLK; //设定几个CLK的时钟源 } struct compx { float real,imag; }; struct compx collect[N],collect0[N]; //AD采集到的值 unsigned long fs = 28500; //采样频率 int i,j,fre; float temp_frc,z; unsigned int t=0,k=0,flag=0,n=0,c=0; /* 函数原型:struct compx EE(struct compx b1,struct compx b2) 函数功能:对两个复数进行乘法运算 输入参数:两个以联合体定义的复数a,b 输出参数:a和b的乘积,以联合体的形式输出 */ struct compx EE(struct compx a,struct compx b) { struct compx c; c.real=a.real*b.real-a.imag*b.imag; c.imag=a.real*b.imag+a.imag*b.real; return(c); } /* 函数原型:void FFT(struct compx *xin) 函数功能:对输入的复数组进行快速傅里叶变换(FFT) 输入参数:*xin复数结构体组的首地址指针,struct型 */ void FFT(struct compx *xin) { int f,m,nv2,nm1,i,k,l,j=0; struct compx u,w,t; int le,lei,ip; //FFT运算核,使用蝶形运算完成FFT运算 nv2=N/2; //变址运算,即把自然顺序变成倒位序,采用雷德算法 nm1=N-1; for(i=0;i if(i t=xin[j]; xin[j]=xin[i]; xin[i]=t; } k=nv2; //求j的下一个倒位序 while(k<=j) //如果k<=j,表示j的最高位为1 { j=j-k; //把最高位变成0 k=k/2; //k/2,比较次高位,依次类推,逐个比较,直到某个位为0 } j=j+k; //把0改为1 } f=N; for(l=1;(f=f/2)!=1;l++) //计算l的值,即计算蝶形级数 ; for(m=1;m<=l;m++) // 控制蝶形结级数 { //m表示第m级蝶形,l为蝶形级总数l=log(2)N le=2<<(m-1); //le蝶形结距离,即第m级蝶形的蝶形结相距le点 lei=le/2; //同一蝶形结中参加运算的两点的距离 u.real=1.0; //u为蝶形结运算系数,初始值为1 u.imag=0.0; w.real=cos(PI/lei); //w为系数商,即当前系数与前一个系数的商 w.imag=-sin(PI/lei); for(j=0;j<=lei-1;j++) //控制计算不同种蝶形结,即计算系数不同的蝶形结 { for(i=j;i<=N-1;i=i+le) //控制同一蝶形结运算,即计算系数相同蝶形结 { ip=i+lei; //i,ip分别表示参加蝶形运算的两个节点 t=EE(xin[ip],u); //蝶形运算,详见公式 xin[ip].real=xin[i].real-t.real; xin[ip].imag=xin[i].imag-t.imag; xin[i].real=xin[i].real+t.real; xin[i].imag=xin[i].imag+t.imag; } u=EE(u,w); //改变系数,进行下一个蝶形运算 } } } void main( void ) { float y[N]; // Stop watchdog timer to prevent time out reset WDTCTL = WDTPW + WDTHOLD; initClock(); initTFT(); REFCTL0 &= ~REFMSTR; // Reset REFMSTR to hand over control to // ADC12_A ref control registers ADC12CTL0 = 0x0050 ; // Internal ref = 1.5V ADC12CTL1 = ADC12SHP; // enable sample timer ADC12MCTL0 = ADC12INCH_13; // ADC i/p ch A10 = temp sense i/p ADC12IE = 0x001; // ADC_IFG upon conv result-ADCMEMO __delay_cycles(75); // 75us delay to allow Ref to settle ADC12CTL0 |= ADC12ENC; P8DIR |= BIT0; P5DIR |= BIT7; P4DIR |= BIT5+BIT6+BIT7; char buffer[81]; etft_AreaSet(0,0,239,319,0);//清屏 sprintf(buffer, "Name:Zhao Zhenqi"); etft_DisplayString(buffer, 0, 0, 0xFFFF, 0x0000); sprintf(buffer, "Student Number:22920132203719"); etft_DisplayString(buffer, 0, 16, 0xFFFF, 0x0000); while(1) { ADC12CTL0 |= ADC12SC; // Sampling and conversion start __bis_SR_register(LPM4_bits + GIE); // LPM0 with interrupts enabled __no_operation(); if(flag==1) { //FFT处理模块 FFT(collect); for(i=0;i temp_frc=collect[1].real; fre=1; for(i=2;i if(collect[i].real>temp_frc)//求出最大幅值对应的频点 { temp_frc=collect[i].real; fre=i; } } fre=fre*fs/N;//所测频率 for(i=0;i<80;i++) { y[i]=sin((float)i*PI*fre/2/fs); } //LCD液晶屏显示模块 etft_AreaSet(32,0,239,319,0);//清屏 sprintf(buffer, "Frequency: %5d Hz",fre); etft_DisplayString(buffer, 0, 32, 0xFFFF, 0x0000); sprintf(buffer,"."); for(i=0;i<80;i++) { etft_DisplayString(buffer,(i)*4,(int)((y[i]+1)*10+90),0xFFFF,0x0000);//绘制波形 } sprintf(buffer,"*"); for(j=1;j<319;j++) { i=14-(int)(collect[j].real/(temp_frc/10)*14);//扫频,频谱高度由该频点与中心频点的相对值决定 etft_DisplayString(buffer,j,200+i,0xFFFF,0x0000);//绘制频谱 } //LED灯显示模块 if(fre>=500&&fre<1500) { P4OUT |= BIT5;//点亮灯 P4OUT &= ~BIT6;//熄灭灯 P4OUT &= ~BIT7; P5OUT &= ~BIT7; } else if(fre>=1500&&fre<2500) { P4OUT &= ~BIT5; P4OUT |= BIT6; P4OUT &= ~BIT7; P5OUT &= ~BIT7; } else if(fre>=2500&&fre<3500) { P4OUT |= BIT5; P4OUT |= BIT6; P4OUT &= ~BIT7; P5OUT &= ~BIT7; } else if(fre>=3500&&fre<4500) { P4OUT &= ~BIT5; P4OUT &= ~BIT6; P4OUT |= BIT7; P5OUT &= ~BIT7; } else if(fre>=4500&&fre<5500) { P4OUT |= BIT5; P4OUT &= ~BIT6; P4OUT |= BIT7; P5OUT &= ~BIT7; } else if(fre>=5500&&fre<6500) { P4OUT &= ~BIT5; P4OUT |= BIT6; P4OUT |= BIT7; P5OUT &= ~BIT7; } else if(fre>=6500&&fre<7500) { P4OUT |= BIT7; P4OUT |= BIT6; P4OUT |= BIT5; P5OUT &= ~BIT7; } else if(fre>=7500&&fre<8500) { P4OUT &= ~BIT7; P4OUT &= ~BIT6; P4OUT &= ~BIT5; P5OUT |= BIT7; } else if(fre>=8500&&fre<9500) { P4OUT |= BIT7; P4OUT &= ~BIT6; P4OUT &= ~BIT5; P5OUT |= BIT7; } else if(fre>=9500&&fre<10500) { P4OUT &= ~BIT7; P4OUT |= BIT6; P4OUT &= ~BIT5; P5OUT |= BIT7; } flag=0; __delay_cycles(2*MCLK_FREQ);//延时 } } } //AD采样模块 #pragma vector = ADC12_VECTOR __interrupt void ACD12_ISR(void) { switch(__even_in_range(ADC12IV,34)) { case 0: break; //Vector 0: 无中断 case 2: break; //Vector 2: ADC溢出中断 case 4: break; //Vector 4: ADC转换时间溢出中断 case 6: collect0[t].imag = 0; collect0[t].real = ADC12MEM0*3.0 / 0x03ff; t++; if(t==N)//采满N个值 { t=0; flag=1; for(k=0;k collect[k]=collect0[k]; } } P8OUT ^=BIT0; __bic_SR_register_on_exit(LPM0_bits); break; //退出低功耗模式0 case 8: break; case 10: break; case 12: break; case 14: break; case 16: break; case 18: break; case 20: break; case 22: break; case 24: break; case 26: break; case 28: break; case 30: break; case 32: break; case 34: break; default: break; } }