原文地址:FFT算法在单片机中的使用&&LCD12864驱动作者:元大帝 好久没更新博客了,觉得对不起自己建立博客的初衷。我这个人太懒了,又没有坚持下去的决心,唉~
言归正传,本次创新基金我是要做一个简易的频谱仪,核心就是要进行一个FFT运算。大家知道,如果采用DSP芯片效果那是相当好的。但由于项目资金以及时间不够等情况,我采用的是ATMEL公司的AVR单片机,这款单片机的FLASH存储和内存比51单片机犀利得多。
由于采用的是12864液晶,也就是一个横128点竖64点的一个点阵,因而采用128点FFT运算已然够了,因为即使得到再多的数据也无法在液晶上可视化显示出来。本文是基于128点FFT运算。
程序如下:
#include
#include
#include
#define N 128
#define PI 3.141592653589
#define uchar unsigned char
#define uint unsigned int
typedef struct
{
int real;
int img;
}complex;
void initw();
//初始化旋转因子
void bitReverse();
//比特反转
void FFT();
complex x[N];
uchar vis[N];
void delayms(uint ms)
{
uint
i,j;
for(i=0;i
void initw() //初始化旋转因子
{
int i;
for (i=0;i
void bitReverse() //比特反转
{
int i,j=0;
int k=0;
int q=0;
complex tmp3;
for (i=0;i>j)&1)*(1<<(6-j));
if(vis[i]==0)
{
tmp3=x[i];
x[i]=x[tmp2];
x[tmp2]=tmp3;
vis[i]=1;
vis[tmp2]=1;
}
}
}
void main()
{
uchar ii,y;
float tmp;
for (ii=0;ii<20;ii++)
{
x[ii].real=3;
x[ii].img=0;
}
for (ii=20;ii<128;ii++)
{
x[ii].real=0;
x[ii].img=0;
}
initw();
bitReverse();
FFT();
while(1);
}
上图是8点FFT运算,按照上图的流程所示,FFT运算主要有两步,一步是比特反转,就是右边不是按照0、1、2、3……这样顺序进行计算的,而左边是的,两边的关系就是进行一个比特反转。可以看到右边0对应二进制为000,左边对应二进制为000,右边1二进制001,左边4对应二进制100,依次下去,可以清楚看到,对于8位FFT运算,对应二进制有三位,而左右两边的关系恰巧是按照中间位进行了个反转。
FFT运算第二步就是乘以旋转因子,注意的是这里是复数运算,虚部和实部都要加入运算。乘以旋转因子后对进行加减运算得到新的值,依次下去得到最终解。
由于单片机内存的限制,因而对于传统的FFT算法,我进行了些改进,原则就是尽量地少使用变量,一个变量可以重复的使用是最理想的了,大家可以在程序中看出。个人意见这是能节省变量最少的了,如果有好的方法,希望可以告诉我下,我的邮箱是albertvictordu@139.com,谢谢!
下面是12864液晶驱动程序的写法:
LCD12864液晶,即像素为128*64的显示液晶。它的每一行横向一共有128个可显示点,每一列纵向有64个,这些“点”其实也都是一个个发光二极管。它可以在一个16*16的点阵区域上显示一个中文,也可以在一个8*16的点阵区域显示一个非中文字符,一般称为半宽字体。即一个中文字所占显示面积是一个非中文字符的两倍。
关于驱动函数的书写,是液晶显示的基础,整个液晶驱动主要有四个函数组成:
1、写命令函数;
2、写数据函数;
3、读状态函数;
4、读数据函数;
这四个函数并不是必须全部写的,具体要看你实现的功能,如果只是单纯的显示汉字和字符,写命令、写数据、读状态这三个函数就够了,如过你还需要进行一些绘图的操作,那读数据函数也必须书写。
另外关于读状态函数,其实也就是用于判忙操作,原则上每次对控制器进行读写操作之前,都必须进行读写检测,由于单片机的操作速度慢于液晶控制器的反应速度,因此可不进行读写检测,或者只进行简短的延时即可。因此,读状态函数也可以不写,只用简短的延时函数替换即可。
单片机用于控制LCD的管脚主要为RS、RW和E管脚,分别的功能是RS为0时,对应单片机访问的是命令寄存器,为1时对应数据寄存器;RW为1时,对应单片机操作为读操作,为0时对应单片机为写操作;E是使能信号。
读操作如下图所示
写操作如下图所示
在12864液晶中,开发商将一些基本指令已经写入到命令寄存器中,我们调用该指令就可以完成相应的功能。
LCD初始化
初始化操作如下:
1.
芯片上电;
2.
延时40ms以上;
3.
复位操作:RST出现一个上升沿(RST=1;RST=0;RST=1;);
4.
功能设定;
5.
延时100us以上;
6.
再次进行功能设定;
7.
延时37us;
8.
显示开关控制;
9.
延时100us以上;
10.
清除显示;
11.
延时10ms以上;
12.
进入点设置;
13.
初始化结束;
LCD液晶屏初始化过程如图所示为:
打点函数
打点函数是创建GUI的基础,打点函数的书写分为以下几个步骤:
1.
进入扩展模式
2.
写入打点地址
3.
读取该地址的数据
4.
修改该地址的数据
5.
将修改后的数据输入LCD中
6.
进入普通模式
GDRAM地址分布情况,需要注意的是横纵坐标的起始地址都是0x80,还有上下半屏的横坐标是不一样的,下半屏的横坐标要加上0x08,而纵坐标跟对应的上半屏的纵坐标是一样的。GDRAM地址分布图,如图所示。
下面的函数是12864与FFT算法的一个结合,里面设置了一个门函数,12864上显示的结果则是一个sinc函数,证明结果是正确的。
#include
#include
#include
#define N 128
#define PI 3.141592653589
#define uchar unsigned char
#define uint unsigned int
#define RS (1<<4)
#define RW (1<<5)
#define EN (1<<6)
//
typedef struct
{
int real;
int img;
}complex;
void initw();
//初始化旋转因子
void bitReverse();
//比特反转
void FFT();
complex x[N];
uchar vis[N];
void delayms(uint ms)
{
uint
i,j;
for(i=0;i
//写数据
void WriteDataLCM(unsigned char WDLCM)
//写数据函数
{
// ReadStatusLCM(); //检测忙
delayms(1);
PORTA|=RS;
//RS=1
delayms(1);
PORTA&=~RW;
//RW=0
delayms(1);
PORTA|=EN;
//EN=1
delayms(1);
PORTB=WDLCM; //输出数据
delayms(1);
PORTA&=~EN;
//EN=0
delayms(1);
}
//写指令
void WriteCommandLCM(unsigned char WCLCM) //写命令函数
{
// ReadStatusLCM(); //根据需要检测忙
delayms(1);
PORTA&=~RS;
//RS=0
delayms(1);
PORTA&=~RW;
//RW=0
delayms(1);
PORTA|=EN;
//EN=1
delayms(1);
PORTB=WCLCM; //输出指令
delayms(1);
PORTA&=~EN;
//EN=0
delayms(1);
}
//读状态:检测忙
void ReadStatusLCM() //读状态函数
{
uchar temp;
uchar flag =
1;
while(flag==1)
{
PORTB=0xff;
delayms(1);
DDRB=0x00;
//端口B改为输入
delayms(1);
PORTA&=~RS;
//RS=0
delayms(1);
PORTA|=RW;
//RW=1
delayms(1);
PORTA|=EN;
//EN=1
delayms(10);
temp = PINB; //读端口B
delayms(10);
DDRB=0xff;
//端口B改为
delayms(10);
PORTA&=~EN;
//EN=0
delayms(1);
if(temp>>7==0)
flag = 0;
}
}
uchar read_data() //读数据函数
{
uchar lcd_data;
PORTB=0xff;
DDRB=0x00;
PORTA|=RW;
PORTA|=RS;
delayms(10);
PORTA|=EN;
delayms(10);
lcd_data=PINB;
delayms(10);
PORTA&=~EN;
DDRB=0xff;
return(lcd_data) ;
}
void point(uchar x,uchar y) //打点函数,最重要的函数,GUI的基础
{
uchar x_Dyte,x_byte;
uchar y_Dyte,y_byte;
uchar
GDRAM_hbit,GDRAM_lbit;
WriteCommandLCM(0x36);
x_Dyte=x/16;
x_byte=x&0x0f;
y_Dyte=y/32;
y_byte=y&0x1f;
WriteCommandLCM(0x80+y_byte);
WriteCommandLCM(0x80+x_Dyte+8*y_Dyte);
read_data();
GDRAM_hbit=read_data();
GDRAM_lbit=read_data();
delayms(10);
WriteCommandLCM(0x80+y_byte);
WriteCommandLCM(0x80+x_Dyte+8*y_Dyte);
delayms(10);
if(x_byte<8)
{
WriteDataLCM(GDRAM_hbit|(0x01<<(7-x_byte)));
WriteDataLCM(GDRAM_lbit);
}
else
{
WriteDataLCM(GDRAM_hbit);
WriteDataLCM(GDRAM_lbit|(0x01<<(15-x_byte)));
}
WriteCommandLCM(0x30);
}
//LCM初始化
void LCMInit(void)
{
WriteCommandLCM(0x30);
//三次显示模式设置,不检测忙信号
delayms(10);
WriteCommandLCM(0x30);
delayms(10);
WriteCommandLCM(0x30);
delayms(10);
WriteCommandLCM(0x30);
//显示模式设置,开始要求每次检测忙信号
WriteCommandLCM(0x08);
//关闭显示
WriteCommandLCM(0x01);
//显示清屏
WriteCommandLCM(0x06);
//显示光标移动设置
WriteCommandLCM(0x0C);
//显示开及光标设置
}
void clear(uchar dat) //清屏函数
{
uchar i,j,k;
uchar addr=0x80;
for(i=0;i<2;i++)
{
for(j=0;j<32;j++)
{
for(k=0;k<8;k++)
{
WriteCommandLCM(0x36);
WriteCommandLCM(0x80+j);
WriteCommandLCM(addr+k);
WriteDataLCM(dat);
WriteDataLCM(dat);
}
}
addr=0x88;
}
WriteCommandLCM(0x36);
WriteCommandLCM(0x30);
}
void heng(uchar a)
{
uchar i;
for(i=0;i<127;i++)
point(i,a);
}
void su(uchar a)
{
uchar i;
for(i=0;i<63;i++)
point(a,i);
}
void FFT()
{
int i,j,k,t,P,B,m;
complex up,down,product;
for (i=0;i<7;i++)
{
B=1<
void initw() //初始化旋转因子
{
int i;
for (i=0;i
void bitReverse() //比特反转
{
int i,j=0;
int k=0;
int q=0;
complex tmp3;
for (i=0;i>j)&1)*(1<<(6-j));
if(vis[i]==0)
{
tmp3=x[i];
x[i]=x[tmp2];
x[tmp2]=tmp3;
vis[i]=1;
vis[tmp2]=1;
}
}
}
void xian(uchar x,uchar y)
{
uchar i;
for(i=63;i>=y;i--)
point(x,i);
}
//主函数
void main(void)
{
uchar ii,y;
float tmp;
//端口初始化
DDRA=0xff;
PORTA=0xff;
DDRB=0xff;
PORTB=0xff;
DDRD=0xff;
PORTD=0x00;
delayms(20);
delayms(20);
LCMInit(); //LCM初始化 //液晶初始化
delayms(100);
clear(0x00);
heng(0);
heng(63);
su(0);
su(127);
for (ii=0;ii<20;ii++)
{
x[ii].real=3;
x[ii].img=0;
}
for (ii=20;ii<128;ii++)
{
x[ii].real=0;
x[ii].img=0;
}
initw();
bitReverse();
FFT();
for(ii=64;ii<128;ii++)
{
tmp=sqrt((x[ii].real*x[ii].real)+(x[ii].img*x[ii].img));
y= 63-(int)tmp;
point(ii-64,y);
xian(ii-64,y);
}
for(ii=0;ii<64;ii++)
{
tmp=sqrt((x[ii].real*x[ii].real)+(x[ii].img*x[ii].img));
y= 63-(int)tmp;
point(ii+64,y);
xian(ii+64,y);
}
while(1);
}
得到的图片:
MATLAB仿真图形: