73-频谱分析256点FFT-USB-CDC送电脑ISP软件调试接口FFT绘图显示,AI8051U实验箱演示程
73-频谱分析256点FFT-USB-CDC送电脑ISP软件调试接口FFT绘图显示,AI8051U实验箱演示程序主程序main.C代码如下(汇编版本在附件中):
/*---------------------------------------------------------------------*/
/* --- Web: www.STCAI.com ---------------------------------------------*/
/* --- BBS: www.STCAIMCU.com-----------------------------------------*/
/* 如果要在程序中使用此代码,请在程序中注明使用了STC的资料及程序 */
/*---------------------------------------------------------------------*/
/*************功能说明 **************
使用AI8051U系列MCU做256点FFT分析, 上传给aiapp-isp显示频谱. MCU主频:40MHz.
ADC使用DMA处理。
使用AI8051U实验箱V1.1验证, 从示波器输入电路输入音频信号, ADC采样后进行FFT计算, 将结果发给aiapp-isp的示波器显示波形.
采样率: 25600Hz, 采样时间10ms
FFT采样点数: 256点.
FFT频率点数: 128.
FFT频谱频率: 0~12700Hz, 分辨率100Hz.
单片机上传数据协议:
上传一帧数据:FB DAT0 DAT1 ..... DAT127 FF
FB:帧数据
DAT0 ... DAT127:128个Y轴数据(数值为0~250),字节序号0~399就是X轴。
FF:帧数据结束
AI8051U @40MHz : 25.6KHz采样率, 采样时间10ms, 加载硬件浮点库+整数库, 只计算6.5ms, 计算+上传 6.8ms, 计算2次上传一次, 显示刷新频率50Hz, 视频级刷新.
******************************************/
//---------------------------------------------------------------------
/************* 本地常量声明 **************/
/************* IO定义声明 **************/
/************* 本地变量声明 **************/
#defineCHANNEL2 //设置ADC通道(咪头通道), 取值为0~7, 对应P1.0~P1.7, 使用别的ADC输入口则要修改ADC初始化函数.
#define SAMPLE_RATE 36000 //定义采样率, 实际采样率为25600Hz, 因为ADC DMA自动触发的时间间隔是ADC完成并且DMA保存数据后开始定时的。实际间隔时间 = ADC时间+DMA保存数据时间+间隔时间
#define SAMPLES 256 //定义采样点数
int xdata adc_buf1; //ADC结果存放的数组, 为每次转换的值, 高字节为ADC通道号,低字节为取平均值后的余数,为平均值
int xdata adc_buf2; //ADC结果存放的数组, 为每次转换的值, 高字节为ADC通道号,低字节为取平均值后的余数,为平均值
bit B_ADC_DMA_ok; //ADC DMA已完成标志(用户标志)
int xdata sample; // ADC采样拷贝
int xdata FFT_Real; // fft的实部
int xdata FFT_Image; // fft的虚部
u16 xdata FFT_A; // 频点的幅度
u8 cal_cnt; //计算次数
u8 edata amplitude; //幅度
bit B_adc_buf; // 0: 操作adc_buf1[], 1: 操作adc_buf2[],
bit B_SampleOk; // 0: 操作adc_buf1[], 1: 操作adc_buf2[],
u8 KeyCode;
/************* 本地函数声明 **************/
#define SCALE 1024 //正弦值、余弦值放大系数
//0~180度余弦表,放大了1024倍(256点FFT时用)
const int COS_TABLE=
{
1024,1024,1023,1021,1019,1016,1013,1009,1004,999, 993, 987, 980, 972, 964, 955,
946, 936, 926, 915, 903, 891, 878, 865, 851,837, 822, 807, 792, 775, 759, 742,
724, 706, 688, 669, 650, 630, 610,590, 569,548, 526, 505, 483, 460, 438, 415,
392, 369, 345, 321, 297, 273, 249, 224, 200,175, 150, 125, 100, 75, 50, 25,
0, -25, -50, -75,-100,-125,-150,-175,-200, -224,-249,-273,-297,-321,-345,-369,
-392,-415,-438,-460,-483,-505,-526,-548,-569, -590,-610,-630,-650,-669,-688,-706,
-724,-742,-759,-775,-792,-807,-822,-837,-851, -865,-878,-891,-903,-915,-926,-936,
-946,-955,-964,-972,-980,-987,-993,-999, -1004,-1009, -1013, -1016, -1019, -1021, -1023,-1024
};
//0~180度正弦表,放大了1024倍(256点FFT时用)
const int SIN_TABLE=
{
0, 25, 50, 75, 100, 125, 150, 175, 200, 224, 249, 273, 297, 321, 345, 369,
392, 415, 438, 460, 483, 505, 526, 548, 569, 590, 610, 630, 650, 669, 688, 706,
724, 742, 759, 775, 792, 807, 822, 837, 851, 865, 878, 891, 903, 915, 926, 936,
946, 955, 964, 972, 980, 987, 993, 999,1004,1009,1013,1016,1019,1021,1023,1024,
1024,1024,1023,1021,1019,1016,1013,1009,1004, 999, 993, 987, 980, 972, 964, 955,
946, 936, 926, 915, 903, 891, 878, 865, 851, 837, 822, 807, 792, 775, 759, 742,
724, 706, 688, 669, 650, 630, 610, 590, 569, 548, 526, 505, 483, 460, 438, 415,
392, 369, 345, 321, 297, 273, 249, 224, 200, 175, 150, 125, 100, 75, 50, 25,
};
//采样存储序列表(倒序) FFT 256点反序十进制:
const u8 LIST_TAB = {
0, 128,64, 192,32, 160,96, 224,16, 144,80, 208,48, 176, 112, 240,
8, 136,72, 200,40, 168, 104, 232,24, 152,88, 216,56, 184, 120, 248,
4, 132,68, 196,36, 164, 100, 228,20, 148,84, 212,52, 180, 116, 244,
12, 140,76, 204,44, 172, 108, 236,28, 156,92, 220,60, 188, 124, 252,
2, 130,66, 194,34, 162,98, 226,18, 146,82, 210,50, 178, 114, 242,
10, 138,74, 202,42, 170, 106, 234,26, 154,90, 218,58, 186, 122, 250,
6, 134,70, 198,38, 166, 102, 230,22, 150,86, 214,54, 182, 118, 246,
14, 142,78, 206,46, 174, 110, 238,30, 158,94, 222,62, 190, 126, 254,
1, 129,65, 193,33, 161,97, 225,17, 145,81, 209,49, 177, 113, 241,
9, 137,73, 201,41, 169, 105, 233,25, 153,89, 217,57, 185, 121, 249,
5, 133,69, 197,37, 165, 101, 229,21, 149,85, 213,53, 181, 117, 245,
13, 141,77, 205,45, 173, 109, 237,29, 157,93, 221,61, 189, 125, 253,
3, 131,67, 195,35, 163,99, 227,19, 147,83, 211,51, 179, 115, 243,
11, 139,75, 203,43, 171, 107, 235,27, 155,91, 219,59, 187, 123, 251,
7, 135,71, 199,39, 167, 103, 231,23, 151,87, 215,55, 183, 119, 247,
15, 143,79, 207,47, 175, 111, 239,31, 159,95, 223,63, 191, 127, 255
};
/********************************************************************
函数功能:进行FFT运算。
入口参数:none.
返 回:none.
********************************************************************/
void FFT(void)
{
u16 i,j;
u16 BlockSize;
int tr,ti;
u8 OffSet1,OffSet2;
long co,si;
for(j=0; j<SAMPLES; j+=2) //先计算2点的
{
tr = sample;
FFT_Real = (sample - tr);
FFT_Image = 0;
FFT_Real = (sample + tr);
FFT_Image = 0;
}
for(BlockSize=4; BlockSize<=SAMPLES; BlockSize<<=1) //再一层层计算
{
for(j=0; j<SAMPLES; j+=BlockSize)
{
for(i=0; i<BlockSize/2; i++)
{
OffSet1 = SAMPLES/BlockSize * i;
co = (long)COS_TABLE;
si = (long)SIN_TABLE;
OffSet1 = i + j;
OffSet2 = OffSet1 + BlockSize/2;
tr = (co*FFT_Real[ OffSet2] + si*FFT_Image) / SCALE;
ti = (co*FFT_Image - si*FFT_Real[ OffSet2]) / SCALE;
FFT_Real[ OffSet2] = (FFT_Real[ OffSet1] - tr) >> 1;
FFT_Image = (FFT_Image - ti) >> 1;
FFT_Real[ OffSet1] = (FFT_Real[ OffSet1] + tr) >> 1;
FFT_Image = (FFT_Image + ti) >> 1;
}
}
}
FFT_Real= FFT_Real>> 1;
FFT_Image = FFT_Image >> 1;
}
//================== USB-CDC返回数据 ======================
void CDC_RetuanData(void)
{
u8 edata *xp;
u8 i,j;
u16 y;
if(++cal_cnt == 2) cal_cnt = 0; //2次刷新一次
for(j=0; j<128; j++)
{
if(cal_cnt == 0) //减慢一点掉落速度
{
if(amplitude != 0) amplitude--; //刷新衰减
}
y = FFT_A; //取幅度
if((y & 0xf800) != 0) y = 2047; // if(y >= 2048) y = 2047; //限幅
i = T_Alaw_encode; //取对数, 借用A率压缩的对数, 用户可以自己决定使用什么对数.
if(amplitude < i) amplitude = i; //值更大, 则更新显示
}
amplitude = 0; //第一个是直流电平(0Hz), 不需要, 写0
if(cal_cnt == 0)
{
TxBuffer = 0xfb; //上传一帧数据:FB DAT0 DAT1 ..... DAT127 FF
y = 1; //发送字节数
xp = &litude; //首地址
amplitude = 0; //直流电平,不是频率信号
for(i=0; i<128; i++)
{
TxBuffer = *xp;
xp++;
}
TxBuffer = 0xff; //命令结束
uart_send(y); //触发发送. 将要发送的数据保存在TxBuffer缓冲区中, 然后调用uart_send(n)函数触发数据发送,参数为发送的字节数. 一次最多可发送64K,函数内部会自动进行USB分包.
}
}
//========================================================================
// 函数: voiddelay_ms(u16 ms)
// 描述: 延时函数。
// 参数: ms,要延时的ms数, 1~65535ms. 自动适应主时钟.
// 返回: none.
// 版本: VER1.0
// 日期: 2013-4-1
// 备注:
//========================================================================
voiddelay_ms(u16 ms)
{
u16 i;
do
{
i = MAIN_Fosc / 6000;
while(--i) ;
}while(--ms);
}
void delay(void)
{
u16 i;
for (i=0; i<100; i++);
}
//========================================================================
// 函数: void ADC_config(void)
// 描述: ADC初始化函数
// 参数: none.
// 返回: none.
// 版本: VER1.0
// 日期: 2024-8-1
// 备注:
//========================================================================
#define D_ADC_POWER (1<<7) /* ADC电源,1开启,0关闭 */
#define D_ADC_START (1<<6) /* 启动转换,自动清0 */
#define D_ADC_FLAG (1<<5) /* 完成标志,软件清0 */
#define D_ADC_EPWMT (1<<4) /* 允许PWMA触发ADC */
#define D_ADC_SPEED 4 /* 0~15, ADC时钟 = SYSclk/2/(n+1) */
#define D_RES_FMT (1<<5) /* ADC结果格式 0: 左对齐, ADC_RES: D9 D8 D7 D6 D5 D4 D3 D2, ADC_RESL: D1 D0 000000 */
/* 1: 右对齐, ADC_RES: 000000D9 D8, ADC_RESL: D7 D6 D5 D4 D3 D2 D1 D0 */
#define CSSETUP (1<<7) /* 0~1,ADC通道选择时间 0: 1个ADC时钟, 1: 2个ADC时钟,默认0(默认1个ADC时钟) */
#define CSHOLD (1<<5) /* 0~3,ADC通道选择保持时间(n+1)个ADC时钟, 默认1(默认2个ADC时钟) */
#define SMPDUTY 23 /* 10~31, ADC模拟信号采样时间(n+1)个ADC时钟, 默认10(默认11个ADC时钟) */
/* ADC转换时间: 10位ADC固定为10个ADC时钟, 12位ADC固定为12个ADC时钟. */
void ADC_config(void)
{
// EAXSFR(); //SFR enable
P1n_pure_input(1<<CHANNEL); //设置要做ADC的IO做高阻输入
ADC_CONTR = D_ADC_POWER + CHANNEL; //ADC on + channel
ADCCFG = D_RES_FMT + D_ADC_SPEED;
ADCTIM = CSSETUP + CSHOLD + SMPDUTY;
// ADC_CONTR |= ADC_START; //启动ADC转换,完成后自动清零
// ADC_CONTR &= ~ADC_FLAG; //清除ADC完成(中断)标志
// ADC_CONTR |= ADC_EPWMT; //允许PWM触发ADC
// EADC= 1; //允许ADC中断
// IPH |= PADCH; //ADC 中断优先级高位
// PADC= 1; //ADC 中断优先级
}
void ADC_DMA_config(void) //初始化ADC DMA函数
{
DMA_ADC_CFG2 = 0; //每个通道转换的次数, 0-->1次, 8-->2次, 9-->4次, 10-->8次, 11-->16次, 12-->32次, 13-->64次, 14-->128次, 15-->256次.
DMA_ADC_CFG= (1<<7) + (0<<2) + 0; //bit7=1: 允许ADC_DMA中断, 0:禁止.bit3 bit2: ADC_DMA中断优先级(低0~3高), bit1 bit0: ADC_DMA数据总线访问优先级(低0~3高)
DMA_ADC_STA= 0; //bit0: ADC_DMA完成(中断请求)标志, 需要软件清0.
DMA_ADC_AMTH = (u8)((SAMPLES-1)/256); //重复次数 = 9DMA_ADC_AMTH*256+DMA_ADC_AMT)+1
DMA_ADC_AMT= (u8)((SAMPLES-1)%256); //
DMA_ADC_CHSW1 = 0; //ADC通道使能, bit7~bit0对应通道15~8.
DMA_ADC_CHSW0 = (1<<CHANNEL); //ADC通道使能, bit7~bit0对应通道7~0.
DMA_ADC_ITVH = (u8)(((MAIN_Fosc + SAMPLE_RATE/2) / SAMPLE_RATE) /256); /*ADC_DMA时间间隔寄存器高字节 */
DMA_ADC_ITVL = (u8)(((MAIN_Fosc + SAMPLE_RATE/2) / SAMPLE_RATE) %256); /*ADC_DMA时间间隔寄存器低字节 */
// DMA_ADC_RXAH = (u8)((u16)(&adc_buf1) /256); //存放的地址
// DMA_ADC_RXAL = (u8)((u16)(&adc_buf1) %256); //存放的地址
// DMA_ADC_CR = (1<<7) + (1<<6); //启动ADC_DMA,bit7=1:允许ADC_DMA功能, 0:禁止. bit6=1:触发ADC_DMA功能.
}
//========================================================================
// 函数: void ADC_DMA_ISR(void) interrupt DMA_ADC_VECTOR
// 描述: ADC DMA中断函数
// 参数: none.
// 返回: none.
// 版本: VER1.0
// 日期: 2024-8-1
// 备注:
//========================================================================
void ADC_DMA_ISR(void) interrupt DMA_ADC_VECTOR
{
if(!B_adc_buf) //第1个缓冲完成, 触发第2个缓冲
{
DMA_ADC_RXAH= (u8)((u16)(&adc_buf2)/256); //存放的地址
DMA_ADC_RXAL= (u8)((u16)(&adc_buf2)%256); //存放的地址
}
else //第2个缓冲完成, 触发第1个缓冲
{
DMA_ADC_RXAH= (u8)((u16)(&adc_buf1)/256); //存放的地址
DMA_ADC_RXAL= (u8)((u16)(&adc_buf1)%256); //存放的地址
}
DMA_ADC_CR = (1<<7) + (1<<6); //启动ADC_DMA,bit7=1:允许ADC_DMA功能, 0:禁止. bit6=1:触发ADC_DMA功能.
DMA_ADC_STA = 0; //清除ADC_DMA完成(中断请求)标志
B_SampleOk = B_adc_buf; //0: 操作adc_buf1[], B1: 操作adc_buf2[]
B_adc_buf = ~B_adc_buf; //指向下一个缓冲
B_ADC_DMA_ok = 1; //标志已完成
}
//==============================================================================================================
// ******************* main() *********************************
//===============================================================================================================
void main(void)
{
u16 i,j;
EAXSFR(); //SFR enable
WTST= 0;
CKCON = 0;
P0n_standard(0xff); P0 = 0xff;
P1n_standard(0xff); P1 = 0xff;
P2n_standard(0xff); P2 = 0xff;
P3n_standard(0xfc); P3 = 0xff;
P4n_standard(0xff); P4 = 0xff;
P5n_standard(0xff); P5 = 0xff;
P6n_standard(0xff); P6 = 0xff;
P7n_standard(0xff); P7 = 0xff;
P54 = 1;
HSCLKDIV = 1; //高速时钟分频器 1~255 (默认2)
TFPU_CLKDIV = 1; //TFPU分频
// MCLKO47_DIV(100); //输出主频分频
DMAIR = 0x3f; //算数运算单元
CLKSEL= 0x00; //下载使用40M, PLL=40M/4*12=120MHz, 给FPU浮点库.
CLKSEL |= 0x80; //内部PLL输出时钟选择, |=0x80: 选择PLL 144MHz, &=~0x80: 选择96MHz(默认)
USBCLK= (USBCLK &~0x60) | (2<<5); //选择PLL输入时钟分频,保证输入时钟为12M左右, 0: 1分频(对应12MHz), 1: 2分频(对应24MHz), 2: 4分频(对应48MHz), 3: 8分频(对应96MHz)
USBCLK |= 0x80; //PLL倍频控制, |=0x80: 使能PLL倍频. &= ~0x80: 禁止PLL倍频
delay(); //等待PLL锁频
CLKSEL |= 0x00; //高速IO时钟源选择, |=0x40: 选择PLLCLK,&=~0x40: 选择MCLK(默认)
CLKDIV= 1; //主时钟分频系数, 1~255,144/5=28.8MHz
// HSCLKDIV = 1; //高速时钟分频器 1~255 (默认2)
// TFPU_CLKDIV = 1; //TFPU分频
CLKSEL |= (0<<2); //主时钟源选择2, 0: MCKSEL选择的时钟源(默认), 1: 内部PLL输出, 2: 内部PLL输出/2, 3: 内部48MHz高速IRC
CLKSEL |=0; //主时钟源选择,0: 内部高精度IRC(默认), 1: 外部高速晶振, 2: 外部32K晶振, 3: 内部32K低速IRC
P3n_pure_input(0x03); //P3.0(D-)、P3.1(D+)设置为高阻
IRC48MCR = 0x80;
while (!(IRC48MCR & 0x01));
uart_init();
usb_init();
EA = 1;
delay_ms(1500);
while(DeviceState != DEVSTATE_CONFIGURED) //等待USB初始化完成
{
NOP(3);
}
ADC_config();
ADC_DMA_config();
for(i=0; i<SAMPLES; i++)
{
FFT_Real = 0; // fft的实部
FFT_Image= 0; // fft的虚部
}
for(i=0; i<SAMPLES/2; i++)
{
FFT_A = 0; // 频率的幅度
amplitude = 0; //幅度
}
B_adc_buf = 0; //第1个缓冲
DMA_ADC_RXAH= (u8)((u16)(&adc_buf1)/256); //ADC DMA存放的地址高字节
DMA_ADC_RXAL= (u8)((u16)(&adc_buf1)%256); //ADC DMA存放的地址低字节
DMA_ADC_CR = (1<<7) + (1<<6); //启动ADC_DMA,bit7=1:允许ADC_DMA功能, 0:禁止. bit6=1:触发ADC_DMA功能.
while(1)
{
if (RxFlag) //当RxFlag为1时,表示已接收到CDC串口数据
//接收的数据大小保存在RxCount里面,每个包最多接收64字节
//数据保存在RxBuffer缓冲区
{
if((RxCount == 4) && (RxBuffer==0xfe) &&(RxBuffer == 0x01) && (RxBuffer == 0xff))
KeyCode = RxBuffer; //PC下传键代码:FE 01 DAT0 FF
uart_recv_done(); //对接收的数据处理完成后,一定要调用一次这个函数,以便CDC接收下一笔串口数据
}
if(B_ADC_DMA_ok) // DMA完成
{
B_ADC_DMA_ok = 0;
if(!B_SampleOk) //第1个缓冲完成, 数据准备188us @40MHz+整数库+浮点库
{
for(i=0,j=0; i<SAMPLES; i++,j+=3) sample] = adc_buf1-2048;
}
else //第2个缓冲完成
{
for(i=0,j=0; i<SAMPLES; i++,j+=3) sample] = adc_buf2-2048;
}
FFT(); //FFT运算 AI8051U 5.5ms @40MHz +整数
for(i=0; i<SAMPLES/2; i++)
{
FFT_A = sqrt((long)FFT_Real*(long)FFT_Real + (long)FFT_Image*(long)FFT_Image); //计算模值, KEIL开平方根670us @40MHz +浮点库.
}
CDC_RetuanData(); //AI8051U @40MHz(+整数), 仅计算115us, 计算+上传 475us
}
}
}
页:
[1]