最近在调试我设计的MP3,用到SPI读写SD卡和向VS1003B发送数据,因为MP3对数据流速度相当敏感,遂对软件SPI进行了优化,目前已能流畅播放了。
我的SPI总线单字节读写函数如下,自认为速度最快了,已至极致,纵然汇编实现,也只能打个平手,51高手请指正。
#include"..incincludes.h"
sbitc_SPI_SI=P1^5;
sbitc_SPI_SO=P1^6;
sbitc_SPI_CLK=P1^7;
#defineMacro_Set_SI_High()c_SPI_SI=1
#defineMacro_Set_SI_Low()c_SPI_SI=0
#defineMacro_Set_CLK_High()c_SPI_CLK=1
#defineMacro_Set_CLK_Low()c_SPI_CLK=0
/*
//----------------标准C语言版-----------------------------------------
//可移植性好,易读,易移植
uint8SD_SPI_ReadByte(void)
{
ucharucReadData;
ucharucCount;
ucReadData=0;
Macro_Set_SI_High();
for(ucCount=0;ucCount<8;ucCount++)
{
ucReadData<<=1;
Macro_Set_CLK_Low();
Macro_Set_CLK_High();
if(c_SPI_SO)
{
ucReadData|=0x01;
}
}
return(ucReadData);
}
voidSD_SPI_WriteByte(uint8ucSendData)
{
ucharucCount;
ucharucMaskCode;
ucMaskCode=0x80;
for(ucCount=0;ucCount<8;ucCount++)
{
Macro_Set_CLK_Low();
if(ucMaskCode&ucSendData)
{
Macro_Set_SI_High();
}
else
{
Macro_Set_SI_Low();
}
Macro_Set_CLK_High();
ucMaskCode>>=1;
}
}
*/
//-------------------------标准优化版SPI读写函数---------
ucharbdataucReadData;
sbitReadData_Bit0=ucReadData^0;
sbitReadData_Bit1=ucReadData^1;
sbitReadData_Bit2=ucReadData^2;
sbitReadData_Bit3=ucReadData^3;
sbitReadData_Bit4=ucReadData^4;
sbitReadData_Bit5=ucReadData^5;
sbitReadData_Bit6=ucReadData^6;
sbitReadData_Bit7=ucReadData^7;
ucharbdataucWriteData;
sbitWriteData_Bit0=ucWriteData^0;
sbitWriteData_Bit1=ucWriteData^1;
sbitWriteData_Bit2=ucWriteData^2;
sbitWriteData_Bit3=ucWriteData^3;
sbitWriteData_Bit4=ucWriteData^4;
sbitWriteData_Bit5=ucWriteData^5;
sbitWriteData_Bit6=ucWriteData^6;
sbitWriteData_Bit7=ucWriteData^7;
uint8SD_SPI_ReadByte(void)
{
//初始化SI引脚状态
Macro_Set_SI_High();
//Bit7ShiftOut
Macro_Set_CLK_Low();
Macro_Set_CLK_High();
ReadData_Bit7=c_SPI_SO;
//Bit6ShiftOut
Macro_Set_CLK_Low();
Macro_Set_CLK_High();
ReadData_Bit6=c_SPI_SO;
//Bit5ShiftOut
Macro_Set_CLK_Low();
Macro_Set_CLK_High();
ReadData_Bit5=c_SPI_SO;
//Bit4ShiftOut
Macro_Set_CLK_Low();
Macro_Set_CLK_High();
ReadData_Bit4=c_SPI_SO;
//Bit3ShiftOut
Macro_Set_CLK_Low();
Macro_Set_CLK_High();
ReadData_Bit3=c_SPI_SO;
//Bit2ShiftOut
Macro_Set_CLK_Low();
Macro_Set_CLK_High();
ReadData_Bit2=c_SPI_SO;
//Bit1ShiftOut
Macro_Set_CLK_Low();
Macro_Set_CLK_High();
ReadData_Bit1=c_SPI_SO;
//Bit0ShiftOut
Macro_Set_CLK_Low();
Macro_Set_CLK_High();
ReadData_Bit0=c_SPI_SO;
return(ucReadData);
}
voidSD_SPI_WriteByte(uint8ucSendData)
{
ucWriteData=ucSendData;
//Bit7ShiftOutToSDCard
Macro_Set_CLK_Low();
c_SPI_SI=WriteData_Bit7;
Macro_Set_CLK_High();
//Bit6ShiftOutToSDCard
Macro_Set_CLK_Low();
c_SPI_SI=WriteData_Bit6;
Macro_Set_CLK_High();
//Bit5ShiftOutToSDCard
Macro_Set_CLK_Low();
c_SPI_SI=WriteData_Bit5;
Macro_Set_CLK_High();
//Bit4ShiftOutToSDCard
Macro_Set_CLK_Low();
c_SPI_SI=WriteData_Bit4;
Macro_Set_CLK_High();
//Bit3ShiftOutToSDCard
Macro_Set_CLK_Low();
c_SPI_SI=WriteData_Bit3;
Macro_Set_CLK_High();
//Bit2ShiftOutToSDCard
Macro_Set_CLK_Low();
c_SPI_SI=WriteData_Bit2;
Macro_Set_CLK_High();
//Bit1ShiftOutToSDCard
Macro_Set_CLK_Low();
c_SPI_SI=WriteData_Bit1;
Macro_Set_CLK_High();
//Bit0ShiftOutToSDCard
Macro_Set_CLK_Low();
c_SPI_SI=WriteData_Bit0;
Macro_Set_CLK_High();
}
网友评论:奉上驱动与测试的UV2工程,便于大家自行测试。
相关链接:/upfiles/img/20083/200833134035403.rar
网友评论:太快了还要加NOP指令
网友评论:首先声明俺对51不熟,所以不敢说下面的办法是否有效,但想给各位提个醒,也许还有很大优化空间。
不知各位注意到了吗?在SD的标准文档中有这样一段话:Bothhostcommandandcardresponseareclockedoutwiththerisingedgeofthehostclock;这就是说数据线(SPI_SI或SPI_SO)的变化可以与时钟线的上升同时进行。
利用这一点,可以在输出数据位时同时拉高时钟线(SPI_CLK),把这两个操作用一条指令完成;也许按照这个思路做,可以省掉一条操作指令,我曾在其它单片机上这样做过,但不知道在51上是否也有效。
网友评论:把核心函数用汇编写,还有什么问题不?
网友评论:hotpower发表于2005-6-180:21:43侃单片机←返回版面
2049:unsignedcharI2CReadWrite(unsignedcharval)
2050:{
2051:unsignedchari;
2052:ACC=val;//取写入数据
C:0x18D3EFMOVA,R7
2053:for(i=8;i>0;i--){//收发8位数据
C:0x18D47F08MOVR7,#0x08
2054:SCL=0;//拉低I2C时钟
C:0x18D6C286CLRSCL(0x80.6)
2055:_rlca_();//左移1位数据(取出1位写入数据)
C:0x18D833RLCA
2056:SDA=CY;//写入1位I2C数据
C:0x18D99287MOVSDA(0x80.7),C
2057:_rrca_();//右移1位还原数据
2058://_nop_();_nop_();_nop_();_nop_();_nop_();//延时4.7uS
2059://如果不追求高速,可在此加入一些总线冲突裁决程序
2060://即SDA=1时,读回若为0则为总线冲突
C:0x18DB13RRCA
2061:SCL=1;//拉高I2C时钟
C:0x18DCD286SETBSCL(0x80.6)
2062:CY=SDA;//读回1位I2C数据
C:0x18DEA287MOVC,SDA(0x80.7)
2063:_rlca_();//左移最后1位数据
C:0x18E033RLCA
2064:}
C:0x18E1DFF3DJNZR7,C:18D6
2065:SCL=0;//拉低I2C时钟
C:0x18E3C286CLRSCL(0x80.6)
2066:returnACC;//返回读回数据
C:0x18E5FFMOVR7,A
2067:}
C:0x18E622RET
相关链接:/club/bbs/showEssence.asp?id=6493&page=1
网友评论:我的代码确实节约了一个机器周期啊!
我最高位是直接读P1读进来的(只要俩周期,你原来的代码要仨周期),虽然其他位B6~B0也一起从P1口上读了(当然是没有意义的),但后面一位一位地换成SPI的数据了。
我换三根线的定义,是为了让变量的最高位与P1口的定义的SI一致。
你原始代码的做法是:B7~B0都用相同的方式输入,各需要6个机器周期
我的办法是:B7节约了一个机器周期,其他位不变。
好比百米比赛,按照你划定的“跑道”,我确实节约了一个周期,怎么就不算了呢???
至于其他的办法,与你的办法差异太大了,不是一个“跑道”,不评价。
当然了,我的代码仅仅在读入时节约了一个周期。
网友评论:你用汇编也不能再节约一个周期了
网友评论:您提到的读P1节省周期,无法理解,因为原创作品,读取SPI芯片的数据输出,只用了一个周期,是将位数据移入位变量时花费了2个机器周期,最后生成CLK占用2个周期,不服还可举证。XWJ改写的SPI读函数代码是通过缩减串转并的周期,故而速度领先。
网友评论:回NE5532,你要是能用汇编写出更快的功能函数,I服了You
回“平常人”,您的思路很不错,将两步合作一步走,是个好主意,能否贴出参考代码,便于大家依葫芦画瓢,如果P1口全部用作输出,您的办法也许真能每位节省1机器周期。
网友评论:/*
//----------------标准C语言版-----------------------------------------
//可移植性好,易读,易移植
uint8SD_SPI_ReadByte(void)
{
ucharucReadData;
ucharucCount;
ucReadData=0;
Macro_Set_SI_High();
ucCount=8;
do{
ucReadData<<=1;
Macro_Set_CLK_Low();
Macro_Set_CLK_High();
if(c_SPI_SO)
{
ucReadData++;
}
}while(--ucCount);
return(ucReadData);
}
voidSD_SPI_WriteByte(uint8ucSendData)
{
ucharucCount;
ucharucMaskCode;
ucCount=8;
do{
Macro_Set_CLK_Low();
Macro_Set_SI_Low();
if(ucSendData&0x01)
{
Macro_Set_SI_High();
}
Macro_Set_CLK_High();
ucSendData>>=1;
}while(--ucCount);
}
网友评论:抄也不是这样抄的吧???
网友评论:主要是基本方法,过程并不重要...
其实是这个上网电脑没有装UE32软件,用的写字板,修改不方便...
网友评论:回37楼
以下是你的原始代码:请看我加的注释
//Bit7ShiftOut
Macro_Set_CLK_Low();
Macro_Set_CLK_High();
ReadData_Bit7=c_SPI_SO;//这个编译以后成了对应于MOVC,c_SPI_SO以及MOVReadData_Bit7,C两条汇编指令的机器码吧?需要1+2=3个周期
以下是改过的
//Bit7ShiftOut
Macro_Set_CLK_Low();
Macro_Set_CLK_High();//已经将P1^5和P1^7的定义对调,到此,最高位出现在P1^7上
ucReadData=P1;//这个编译以后成了对应于MOVucReadData,P1一条汇编指令的机器码吧?只需要2个周期。注意:我把P1的整个一个byte读进来了,当然P1^7也读进来了,而且就读到了ucReadData的最高位上也就是读到了ReadData_Bit7里(读进来的其它位当然没有意义,后面还是要一位一位补上的)!
怎么没有道理呢?
网友评论:看了楼上这些大虾的一番番激论﹐心里真是贼佩服﹗﹗
同时心有感慨﹐什么时候自己才能够参与这样的话题。。。。N年以后﹗﹗
我刚开始学汇编﹐用的是义隆E8-ICE。。。想让大虾们给点建议…..
学义隆用没有发展﹖前景好吗﹖﹖
网友评论:都是学生时代玩的东西
网友评论:看看你的所谓的“精华”贴,那才真正的是学生做的程序
如果你的产品只能做到这种水平的话,估计是没几个人敢买的...
相关链接:/club/bbs/showEssence.asp?id=2636
网友评论:您将P1口的数据读到ucReadData,这是能节省一个周期,但ucReadData还不是最终的结果,即使将最高位移位到D6位,为下一次读做准备,况且下一次读又会改变ucReadData的内容,所以局部节省不代表整个函数节省。
网友评论:貌似很多高手和考官爱那他的好算法来贬低人.
卖出去的产品人家不会看你怎么写的程序~~~~~~~~~~
网友评论:说软件模拟SPI没有意义,此言差矣。SPI的扩展芯片极为常见,以其高速通讯和连线简化,大有取代传统并行总线接口器件之势。
很多单片机没有
硬件SPI接口,软件摸拟提高速度是有好处的。比如传统字库用并行Flash29C020,改用
AT45DB161后,读取字库不优化,效果很不理想。
又比如LCD屏幕,很多小屏采用了SPi接口,如果驱动函数不做优化,整屏更新数据会明显的停顿感,更不要说做绘图设计了。
网友评论:但是这种方法在51上是最快的方法了,也是KeilC预留的一个经典方法,相关的头文件如下
#define_setc_()CY=1
#define_clrc_()CY=0
#define_setb_(RBIT)RBIT=1
#define_clrb_(RBIT)RBIT=0
#define_clra_()ACC=0
#define_movcb_(RBIT)CY=RBIT
#define_movbc_(RBIT)RBIT=CY
#define_movra_(RX)RX=ACC
#define_movar_(RX)ACC=RX
#define_movb0_(RBIT,RX)RBIT=RX&0x01//用于取RX的最低位
#define_movb7_(RBIT,RX)RBIT=RX&0x80//用于取RX的最高位
#define_rrca_()CY=ACC&0x01//产生RRCA指令
#define_rlca_()CY=ACC&0x80//产生RLCA指令
#define_rrcar_(RX)CY=RX&0x01
#define_rlcar_(RX)CY=RX&0x80
#define_xorr_(RX)RX^=RX//用于取奇偶位P,且RX=0
#define_andr_(RX)RX&=RX//用于取奇偶位P,且RX=不变
#define_orr_(RX)RX|=RX//用于取奇偶位P,且RX=不变
#define_notr_(RX)RX=~RX
网友评论:因为位操作可以用MOVE指令,要是PIC可以把你急死。菜农的C语言精简到差不多了,要更快,何必用软口了呢?
网友评论:回50楼
咋就局部节省不等于整体节省了呢?我改过以后,别的地方并没有动,也就没有在别的地方多用机器周期。我改过的函数你调用一次就是比原来的少用了一个机器周期的,难道不是吗?那你说说,改了以后,用多少个机器周期?
我的局部难道影响整体了吗?
我只是在取D7位的时候节省了一个周期,其它的位还是原始代码,原始代码效率确实是很高了,我没指望节省多的机器周期,一个,就一个,也是省了。
网友评论:再更正一下50楼的说法
是最高位D7,不是D6,注意我把P1.5和P1.7的定义对调了,就是为了取D7的时候少用一个机器周期。
网友评论:即使有SPI...因为它的地基不好~~~铺金也白搭~~~
网友评论:现在8块以内的Arm有吗?象一些简单应用,还是用51比较好,简单易用,很多底层代码可以网络上下载。
另外,除非你是个刚开门的公司,否则如果原来的产品是51做的,那么你的产品必然有一定的继承性,或者叫兼容性,并不是说Arm好用,就可以把一个公司里面所有的东西重新开发一遍,你想,所有的硬件驱动代码要重新写工作量就很可怕,就算能够重新开发,售后服务怎么做。你的客户会怎么想,硬件只是一个平台,能表达你的设计思想,够用就可以了。难道你出门上班,有部夏利的士过来,你就是不坐,非帕萨特的士不可吗?
呵呵。。别介意。。不是对你的攻击,对你的观点的一种不同意的表达
网友评论:您还不服气,您可以将您的思想改个完整代码试试,您就会发现错误的,好多东西光凭想象是不可靠的。
Hotpower的观点是完美主义,Winds代表实用主义,俺的观点是逐步追求完美。
先实现功能,再做优化。
网友评论:再精简代码,也只是局限于用软件一位一位的模拟发送和接收,思想已经局限于这个框架了。我现在操作SPI接口器件是用CPLD来实现的,单片机用并口向CPLD写一个字节数据后,CPLD依靠晶振给提供的脉冲极快的就把数据发送给接口器件,单片机只需要等待2个NOP就可以写下一个字节数据了,比单片机提供的真正的SPI接口还要快,完全是两种档次的思想,这个问题没什么讨论的价值。
网友评论:串口的UART0方式有我做的方式快么
网友评论:PDF介绍,该芯片在6时钟模式下,最大外频为16M,俺整了个18.432M的
晶体,小超一把,嘿嘿,用得蛮好的。俺相信再用上它的硬件SPI,估计能将256KbpS的Mp3播放出来。
网友评论:如果不是程序容量不是很大,可以试试看,成本也不高,听说才6块多
网友评论:其实,在做技术的过程中,总是有很多的这样的小问题,总是在困扰着我们!
看了一下这个题目,我自己没有想到更好的办法出来!
从对于这些问题的理解,就可以看到人的水平是不同的!
网友评论:我没有不服气,是你不服气吧?(因为你的NO.1???)
我当然做了测试,还是不相信的话,你可以要求我发编译过程中形成的反汇编码子。
网友评论:最好附上您修改好的uV2工程,让大家给您分析分析。我已上传采用XWJ代码建立的uV2工程,修改和测试很方便,见前述发贴。
网友评论:我也在做SD卡,但不是MP3,现在也差不多了。我的设计对速度要求不是很高,所以可以读写就行了!
今天在这里向各位学习了!
网友评论:回71楼,我只看你的代码啊!
我就那点能耐,就省了一个机器周期,呵呵
我的UV工程和你的UV工程就差那一句,以及P1.5,P1.7定义对调。
网友评论:XWJ读函数好!
不过同理,可以节约一个机器周期。XWJ已经在代码里给出注释了。
网友评论:直接操作一个完整字节,才是软件模拟最快的方法=======单刀直入。
呵
网友评论:ucharMMC_SPI(dataucharcmd)
{
datauchari,j=0;
//ACC=0;
for(i=8;i>0;i--)
{
j<<=1;
MMC_SCK=0;
j|=MMC_DO;
MMC_DI=(cmd&0x80)==0x80;
//MMC_DI=cmd>>7;
MMC_SCK=1;
cmd<<=1;
}
//MMC_SCK=1;
return(j);
}
ucharMMC_SPI(dataucharcmd)
;15.{
MMC_SPI:
;16.datauchari,j=0;
;17.//ACC=0;
;18.for(i=8;i>0;i--)
MOV$LOCBDMMC_SPI+1,#0
MOV$LOCBDMMC_SPI,#8
MOV$LOCBDMMC_SPI+2,R4
?0001:
MOVA,$LOCBDMMC_SPI
JZ?0000
?0002:
;19.{
;20.j<<=1;
MOVA,$LOCBDMMC_SPI+1
ADDA,ACC
MOV$LOCBDMMC_SPI+1,A
;21.MMC_SCK=0;
CLR232.1
;22.j|=MMC_DO;
CLRA
MOVC,232.0
MOVACC.0,C
ORL$LOCBDMMC_SPI+1,A
;23.MMC_DI=(cmd&0x80)==0x80;
MOVA,$LOCBDMMC_SPI+2
ANLA,#128
XRLA,#128
JZ?0031
MOVA,#255
?0031:
INCA
MOVC,ACC.0
MOV232.2,C
;24.//MMC_DI=cmd>>7;
;25.MMC_SCK=1;
SETB232.1
;26.cmd<<=1;
MOVA,$LOCBDMMC_SPI+2
ADDA,ACC
MOV$LOCBDMMC_SPI+2,A
DEC$LOCBDMMC_SPI
SJMP?0001
?0000:
;27.}
;28.//MMC_SCK=1;
;29.return(j);
MOVR4,$LOCBDMMC_SPI+1
;30.}
网友评论:最接近硬件SPI的特性,只是俺的应用是分开的,读数据时,不管写入什么东西,写数据时,不管返回的数据。
网友评论:因为其生成的DJNZ汇编指令循环模式是很不错的。
网友评论:IAR的编译器喜欢预分配一个堆,局部变量一般都在堆里分配,较少用到
寄存器。不过IAR文件越大优化效果越明显,而且不出错,KEILC51在编译超过20多K后8~9级优化便容易出一些问题。表现形式是液晶上显示的字符乱码(同样的代码低优化级别正常)
网友评论:看了也不记得,能全在一楼上下看最好
网友评论:难道cpu一直在这模拟?
cpu忙其他事的时候,速度立马变成0,或者除非外部有缓冲,
硬件并且带fifo才有意义。不然也只有死等