基于51单片机的SPI总线基于51单片机的SPI总线单片机与其它芯片或设备之间的数据传输在单片机的应用中具有重要的地位,单片机本身的数据传输接口过去主要为8位并行数据接口或异步串行通信接口,但电子技术的迅速发展使得许多新的数据传输接口标准不断涌现,大多数的51单片机并没有在硬件中集成这些新的数据传输接口。SPI(SerialPeripheralInterface)总线是由Motorola公司提出的一种同步串行外围接口,采用三或四根信号线。51单片机一般并没有在硬件中集成这种新的接口,所以要用软件来进行模拟。1硬件设计DS1302是涓流充电时钟芯片,内含有一个实时时钟/日历和31字节静态RAM,实时时钟/日历电路提供秒、分、时、日、星期、月、年的信息,每月的天数和闰年的天数可自动调整,时钟操作可通过AM/PM指示决定采用24或12小时格式。DS1302与单片机之间能简单地采用SPI同步串行的方式进行通信,仅需用到三根信号线:RES(复位),I/O(数据线),SCLK(同步串行时钟)。通过1602LCD显示日期和时间,其电路如下所示。在桌面上双击图标,打开ISIS7Professional窗口(本人使用的是v7.4SP3中文版)。单击菜单命令“文件”→“新建设计”,选择DEFAULT模板,保存文件名为“SPI.DSN”。在器件选择按钮中单击“P”按钮,或执行菜单命令“库”→“拾取元件/符号”,添加如下表所示的元件。51单片机AT89C51一片晶体CRYSTAL12MHz一只瓷片电容CAP22pF二只电解电容CAP-ELEC10uF一只电阻RES10K一只排阻RESPAC-810K一只1602液晶显示器LM016L一只晶体CRYSTAL32.768KHz一只时钟芯片DS1302一片电池BATTERY3V一只若用Proteus软件进行仿真,则上图中的两只晶体、U1的复位电路和U1的31脚以及电池都可以不画,它们大都是默认的。在ISIS原理图编辑窗口中放置元件,再单击工具箱中元件终端图标,在对象选择器中单击POWER或GROUND放置电源或地。放置好元件后,布好线。左键双击各元件,设置相应元件参数,完成电路图的设计。2软件设计采用AT89C51以及日历芯片DS1302和1602LCD组成时钟的流程图如下所示。本例主要目的是如何用软件模拟SPI总线对DS1302进行读、写,其详细详细C51程序如下。//实例:基于DS1302的日历时钟#includereg51.h//包含单片机寄存器的头文件#includeintrins.h//包含_nop_()函数定义的头文件/*********************************以下是DS1302芯片的操作程序**********************************/unsignedcharcodedigit[10]={0123456789};//定义字符数组显示数字sbitDATA=P1^1;//位定义1302的数据输出端定义在P1.1引脚sbitRST=P1^2;//位定义1302的复位端口定义在P1.2引脚sbitSCLK=P1^0;//位定义1302的时钟输出端口定义在P1.0引脚/*****************************函数功能:延时若干微秒入口参数:n******************************/voiddelaynus(unsignedcharn){unsignedchari;for(i=0;in;i++);}/**********************************函数功能:向1302写一个字节数据入口参数:dat***********************************/voidWrite1302(unsignedchardat){unsignedchari;SCLK=0;//拉低SCLK,为脉冲上升沿写入数据做好准备delaynus(2);//稍微等待,使硬件做好准备for(i=0;i8;i++)//连续写8个二进制位数据{DATA=dat&0x01;//取出dat的第0位数据写入1302delaynus(2);//稍微等待,使硬件做好准备SCLK=1;//上升沿写入数据delaynus(2);//稍微等待,使硬件做好准备SCLK=0;//重新拉低SCLK,形成脉冲dat=1;//将dat的各数据位右移1位,准备写入下一个数据位}}/***********************************************函数功能:根据命令字,向1302写一个字节数据入口参数:Cmd,储存命令字;dat,储存待写的数据************************************************/voidWriteSet1302(unsignedcharCmd,unsignedchardat){RST=0;//禁止数据传递SCLK=0;//确保写数居前SCLK被拉低RST=1;//启动数据传输delaynus(2);//稍微等待,使硬件做好准备Write1302(Cmd);//写入命令字Write1302(dat);//写数据SCLK=1;//将时钟电平置于已知状态RST=0;//禁止数据传递}/********************************函数功能:从1302读一个字节数据出口参数:dat*********************************/unsignedcharRead1302(void){unsignedchari,dat;delaynus(2);//稍微等待,使硬件做好准备for(i=0;i8;i++)//连续读8个二进制位数据{dat=1;//将dat的各数据位右移1位if(DATA==1)//如果读出的数据是1dat|=0x80;//将1取出,写在dat的最高位SCLK=1;//将SCLK置于高电平,为下降沿读出delaynus(2);//稍微等待SCLK=0;//拉低SCLK,形成脉冲下降沿delaynus(2);//稍微等待}returndat;//将读出的数据返回}/**********************************************函数功能:根据命令字,从1302读取一个字节数据入口参数:Cmd出口参数:dat**********************************************/unsignedcharReadSet1302(unsignedcharCmd){unsignedchardat;RST=0;//拉低RSTSCLK=0;//确保写数居前SCLK被拉低RST=1;//启动数据传输Write1302(Cmd);//写入命令字dat=Read1302();//读出数据SCLK=1;//将时钟电平置于已知状态RST=0;//禁止数据传递returndat;//将读出的数据返回}/********************************函数功能:1302进行初始化设置*********************************/voidInit_DS1302(void){WriteSet1302(0x8E,0x00);//写入不保护指令WriteSet1302(0x80,((0/10)4|(0%10)));//写入秒的初始值WriteSet1302(0x82,((0/10)4|(0%10)));//写入分的初始值WriteSet1302(0x84,((12/10)4|(12%10)));//写入小时的初始值WriteSet1302(0x86,((24/10)4|(24%10)));//写入日的初始值WriteSet1302(0x88,((4/10)4|(4%10)));//写入月的初始值WriteSet1302(0x8c,((10/10)4|(10%10)));//写入年的初始值}/*******************************以下是对液晶模块的操作程序********************************/sbitRS=P2^0;//寄存器选择位,将RS位定义为P2.0引脚sbitRW=P2^1;//读写选择位,将RW位定义为P2.1引脚sbitE=P2^2;//使能信号位,将E位定义为P2.2引脚sbitBF=P0^7;//忙碌标志位,,将BF位定义为P0.7引脚/*******************************************************************函数功能:延时1ms(3j+2)*i=(3×33+2)×10=1010(微秒),可以认为是1毫秒********************************************************************/voiddelay1ms(){unsignedchari,j;for(i=0;i10;i++)for(j=0;j33;j++);}/*****************************函数功能:延时若干毫秒入口参数:n*******************************/voiddelaynms(unsignedcharn){unsignedchari;for(i=0;in;i++)delay1ms();}/***********************************************函数功能:判断液晶模块的忙碌状态返回值:result。result=1,忙碌;result=0,不忙***********************************************************/bitBusyTest(void){bitresult;RS=0;//根据规定,RS为低电平,RW为高电平时,可以读状态RW=1;E=1;//E=1,才允许读写_nop_();//空操作_nop_();_nop_();_nop_();//空操作四个机器周期,给硬件反应时间result=BF;//将忙碌标志电平赋给resultE=0;//将E恢复低电平_nop_();_nop_();_nop_();_nop_();returnresult;}/**************************************************************函数功能:将模式设置指令或显示地址写入液晶模块入口参数:dictate***************************************************************/voidWriteInstruction(unsignedchardictate){while(BusyTest()==1);//如果忙就等待RS=0;//根据规定,RS和R/W同时为低电平时,可以写入指令RW=0;E=0;//E置低电平,为了让E从0到1发生正跳变,所以应先置0_nop_();_nop_();//空操作两个机器周期,给硬件反应时间P0=dictate;//将数据送入P0口,即写入指令或地址_nop_();_nop_();_nop_();_nop_();//空操作四个机器周期,给硬件反应时间E=1;//E置高电平_nop_();_nop_();_nop_();_nop_();//空操作四个机器周期,给硬件反应时间E=0;//当E由高电平跳变成低电平时,液晶模块开始执行命令_nop_();_nop_();_nop_();_nop_();}/*********************************************函数功能:指定字符显示的实际地址入口参数:x*****************************************